程序: 是为完成特定任务,用某种语言编写的一组指令的集合,即指一段静态代码 进程:
线程;
一个进程中多个线程共享相同的内存单元/内存地址空间->他们从同一堆中分配对象,可以访问相同的变量和对象,这就使得线程间的通信更加简单、高效,但是多个线程操作共享的系统资源可能带来安全问题
继承Thread类创建线程
如下创建一个线程调用0-100的偶数
public class Main {
public static void main(String[] args) {
//创建线程对象
MyThread p = new MyThread();
//启动线程
p.start();
System.out.println("hellowordhhhhh");
}
}
class MyThread extends Thread{
@Override
public void run() {
//线程的功能
for (int i = 0;i<10;i++){
if(i%2==0){
System.out.println(Thread.currentThread().getName()+i);
}
}
}
}
实现Runnable接口创建线程
public class Main {
public static void main(String[] args) {
//创建MyThread对象
MyThread p = new MyThread();
//创建线程对象
Thread t1 = new Thread(p);
t1.start();
System.out.println("hellowordhhhhh");
}
}
class MyThread implements Runnable{
@Override
public void run() {
//子线程
for (int i = 0;i<10;i++){
if(i%2==0){
System.out.println(Thread.currentThread().getName()+i);
}
}
}
}
实现Callable接口创建线程 与Runnable相比Callable功能要强大一些
示例如下。
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Main {
public static void main(String[] args) {
//3.创建Callable接口实现类 的对象
NumThread numThread = new NumThread();
//4.将Callable接口实现类 的对象作为参数传递到FutureTask构造器中,创建FutureTask对象
FutureTask futureTask = new FutureTask(numThread);
//5.将FutureTask的对象作为参数传递到Thread类的构造器中,调用start()
new Thread(futureTask).start(); //开启线程
try {
//6.可选,获取call方法的返回值
Object sum = futureTask.get();//get方法的返回值为 call()方法的返回值
System.out.println(sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
/*
1.实现Callable接口
2.重写call方法 编写该线程的逻辑
*/
class NumThread implements Callable {
@Override
public Object call() throws Exception {
//遍历100以内的偶数,并返回偶数总和
int sum = 0;
for(int i = 1;i<=100;i++){
if(i % 2 == 0){
System.out.println(i);
sum += i;
}
}
return sum;
}
}
使用线程池的方式创建线程
传统的方式创建线程,经常创建和销毁,使用量特别打的资源,比如并发情况下对性能影响很大。 使用线程池的好处是,提前创建多个线程,放入到线程池中,使用时直接获取,使用完毕后放回线程池中,可以避免频繁创建销毁,实现重复利用,便于线程管理。
package com.company;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Main {
public static void main(String[] args) {
//1.提供指定线数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
//设置线程池的属性
ThreadPoolExecutor serviceSet = (ThreadPoolExecutor) service;
// serviceSet.setCorePoolSize();//
// serviceSet.setKeepAliveTime();
//2.执行指定线程的操作,需要提供实现Runnable或Callable接口实现类的对象
service.execute(new NumThread());//Runnable的方式
//service.submit(new NumThread());//Callable的方式
//3.关闭连接池
service.shutdownNow();
}
}
//实现 Runnable接口
class NumThread implements Runnable{
@Override
public void run() {
for(int i = 0;i<=100;i++){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}
继承Thread与实现Runnable两种创建线程的区别
开发中优先选择:实现Runnable接口的方式,主要有如下优势
实现的方式更适合来处理多个线程有共享数据的情况
一个线程重创建到销毁经历的过程
一个进程中多个线程共享相同的内存单元/内存地址空间->他们从同一堆中分配对象,可以访问相同的变量和对象,这就使得线程间的通信更加简单、高效,但是多个线程操作共享的系统资源可能带来安全问题 如下多窗口卖票程序,出现的重票,错票问题
public class Main {
public static void main(String[] args) {
//创建MyThread对象
MyThread p = new MyThread();
//创建线程对象
Thread t1 = new Thread(p);
Thread t2 = new Thread(p);
Thread t3 = new Thread(p);
t1.start();
t2.start();
t3.start();
}
}
class MyThread implements Runnable{
public int ticket = 10;
@Override
public void run() {
while(true){
if(ticket > 0){
System.out.println(Thread.currentThread().getName()+"卖票,票号为"+ticket);
ticket--;
}else{
break;
}
}
}
}
可以看到8号票出现了重票问题,这就是一个典型的线程安全问题
在Java中通过同步机制解决安全问题
方式一:同步代码块 同步监视器:可为任意一个对象 、this、属于的类名.class 多个线程必须使用同一个同步监视器
synchronized(同步监视器){
//需要被同步的代码
//操作共享数据的代码
}
```
public class Main {
public static void main(String[] args) {
//创建MyThread对象
MyThread p = new MyThread();
//创建线程对象
Thread t1 = new Thread(p);
Thread t2 = new Thread(p);
Thread t3 = new Thread(p);
t1.start();
t2.start();
t3.start();
}
}
class MyThread implements Runnable{
public int ticket = 20;
public Object key = new Object();
@Override
public void run() {
while(true){
synchronized(key){
//this
//MyThread.class
//key
if(ticket > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"卖票,票号为"+ticket);
ticket--;
}else{
break;
}
}
}
}
}
解决线程安全问题
方式二:同步方法
只需将需要同步的代码放在一个方法里,并给这个方法添加synchronized
即可
同步方法的方式默认同步监视器为this
public class Main {
public static void main(String[] args) {
//创建MyThread对象
MyThread p = new MyThread();
//创建线程对象
Thread t1 = new Thread(p);
Thread t2 = new Thread(p);
Thread t3 = new Thread(p);
t1.start();
t2.start();
t3.start();
}
}
class MyThread implements Runnable{
public int ticket = 20;
public Object key = new Object();
@Override
public void run() {
while(true){
show();
}
}
private synchronized void show(){
if(ticket > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"卖票,票号为"+ticket);
ticket--;
}
}
}
这里是将同步代码抽离出来,单独设置方法为 synchronized
是因为如果将synchronized
添加到run上,程序会变成“单线程”,当然只是针对本例而言,如果实际中run方法都是同步操作,就可以将run设置为synchronized
。
需要注意的是如果使用的是继承Thread类的方法创建的线程,需要将
synchronized
方法设置为静态方法,此时的同步监视器为当前类,而不是this
。 方式三:Lock锁 1.创建lock对象private ReentrantLock lock = new ReentrantLock();
2.开启锁lock.lock()
3.解锁lock.unlock()
public class Main {
public static void main(String[] args) {
//创建MyThread对象
MyThread p = new MyThread();
//创建线程对象
Thread t1 = new Thread(p);
Thread t2 = new Thread(p);
Thread t3 = new Thread(p);
t1.start();
t2.start();
t3.start();
}
}
class MyThread implements Runnable{
public int ticket = 20;
//1.实例化 lock
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while(true){
try{
//2.调用lock
lock.lock();
if(ticket > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"卖票,票号为"+ticket);
ticket--;
}else{
break;
}
}finally {
//3.解锁方法
lock.unlock();
}
}
}
}
synchronized与lock方式的区别 synchronized机制在执行完同步代码的时候,自动释放同步监视器 lock需要手动启动同步(lock()),同时结束时也需要手动解锁(unlock())
线程的死锁问题 不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。 出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续。
解决办法: 尽量减少同步资源的定义 尽量避免嵌套同步
wait()
:一旦执行该方法,当前线程就进入阻塞状态,并释放同步监视器
notify()
:执行该方法会唤醒一个被wait的一个线程,如果有多个线程被await就唤醒优先级高的那个
notifyAll()
:唤醒所有被wait的线程
这三个方法进行线程通信时必须在同步代码块或同步方法中使用
这三个方法的调用者必须是同步代码块或同步方法中的同步监视器
sleep()和wait() 相同点:一旦执行都可以使当前线程进入阻塞状态 不同点: (1)两个方法声明的位置不同,Thread类声明sleep(),Object类中声明wait() (2)调用的要求不同,sleep()可以在任何需要的场景下调用,wait()必须使用同步代码块,或同步方法中 (3)是否释放同步监视器,如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁