java多线程,线程同步

多线程

在一个应用程序中,存在多个线程,不同的线程可以并行执行任务
优点:

提高程序处理能力

提高cpu的利用率

改善程序结构,将复杂任务分为多个线程,独立运行

缺点:

线程多,占用内存也多

多线程需要协调和管理,需要跟踪管理线程,cpu开销变大

线程之间会对共享资源访问相互影响,如果不加以控制会导致数据出错(比如龟兔赛跑问题中,兔子乌龟同时走到1000步到达终点)

线程同步:

为防止多线程对共享资源访问产生影响,需要“同步”机制限制线程先来后到执行

同步也就是“排队”和“锁”

线程之间排队,按顺序对共享资源进行操作而不同时操作

用锁来保证数据在方法中被访问时的正确性

锁:

确保一个时间点只有一个线程访问共享资源,哪个线程获取了锁,才有权力访问该共享资源

synchronized(同步锁){
	//需要被同步的代码块
}
public synchronized void show(String name){
    //需要被同步的代码块
}
实际应用:

买票问题,两个窗口买票

@Override
    public void run(){
        while(true) {
                if (t > 0) {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println((Thread.currentThread().getName() + "抢到了" + t + "票"));
                    t--;
                }
                else{
                    break;
                }
        }
    }

test:

		TicketThread ticketThread1=new TicketThread();
        TicketThread ticketThread2=new TicketThread();
        ticketThread1.setName("t1");
        ticketThread2.setName("t2");
        ticketThread1.start();
        ticketThread2.start();
/*
t1抢到了10票
t2抢到了10票
t2抢到了8票
t1抢到了8票
t2抢到了6票
t1抢到了6票
t2抢到了4票
t1抢到了4票
t1抢到了2票
t2抢到了2票
t1抢到了0票
*/

发现会有两个窗口买到同一张票的情况,所以需要synchronized来锁住先进来访问的线程

	static int t=10;
    static Object object=new Object();
    @Override
    public void run(){
        while(true) {
            //synchronized (object) {//加锁后别的只有一个线程访问共享资源
                if (t > 0) {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println((Thread.currentThread().getName() + "抢到了" + t + "票"));
                    t--;
                }
                else{
                    break;
                }
            //}
        }
    }

同步对象: 对多个线秤对应的对象必须是同一个
用来记录有没有线程进入到同步代码块中的,在对象的对象头中有一块空向用来记录有没有线程进入到同步代码块

同步对象可以是Java中任何类的对象

锁方法:

synchronized修饰方法

同步对象会有默认的

synchronized如果修饰的是非静态方法,那么同步对象就是this

synchronized如果修饰的是静态方法,那么同步对象就是当前类的Class对象。

非静态方法:

public  synchronized void printTicket(){
        while(true) {
                if (t > 0) {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println((Thread.currentThread().getName() + "抢到了" + t + "票"));
                    t--;
                }
                else{
                    break;
                }
            }
        }
/*
t1抢到了10票
t2抢到了10票
t1抢到了8票
t2抢到了8票
t1抢到了6票
t2抢到了6票
t1抢到了4票
t2抢到了4票
t1抢到了2票
t2抢到了2票
t1抢到了0票
*/

静态方法:

public static synchronized void printTicket(){
        while(true) {
                if (t > 0) {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println((Thread.currentThread().getName() + "抢到了" + t + "票"));
                    t--;
                }
                else{
                    break;
                }
            }
        }
发现正常买到票
用任务接口synchronized:
int t=1000;
    @Override
    public void run(){
        while(true) {
            synchronized (this) {//加锁后别的只有一个线程访问共享资源
                if (t > 0) {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println((Thread.currentThread().getName() + "抢到了" + t + "票"));
                    t--;
                }
                else{
                    break;
                }
            }
        }
    }

test:

		TicketTask ticketTask=new TicketTask();
        Thread t1=new Thread(ticketTask,"t1");
        Thread t2=new Thread(ticketTask,"t2");
        t1.start();
        t2.start();

成员变量可以不用static,synchronized也可以不用object而用this,因为此时只用创建一个对象

第二种加锁方式Lock

使用ReentrantLock类实现加锁

lock加锁,unlock释放锁

释放锁要在finally,因为如果加完锁后出现异常后面就不执行了,就不能释放锁了,线程就被卡住了,放在finally里出现异常捕获后还可以执行finally的语句从而释放锁便于执行别的线程

LockThread:

public class LockThread implements Runnable{
    int t=10;
    ReentrantLock reentrantLock=new ReentrantLock();
    @Override
    public void run(){
        while(true) {
                if (t > 0) {
                    try {
                        reentrantLock.lock();
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }finally {
                        reentrantLock.unlock();
                    }
                    System.out.println((Thread.currentThread().getName() + "抢到了" + t + "票"));
                    t--;
                }
                else{
                    break;
                }
        }
    }
}

Test:

		LockThread lockThread=new LockThread();
        Thread t1=new Thread(lockThread,"t1");
        Thread t2=new Thread(lockThread,"t2");
        t1.start();
        t2.start();
/*
t1抢到了10票
t2抢到了9票
t1抢到了8票
t2抢到了7票
t1抢到了6票
t2抢到了5票
t1抢到了4票
t2抢到了3票
t1抢到了2票
t2抢到了1票
t1抢到了0票
*/
两个方法不同点:

synchronization:同步 reentrant:折返

synchronized是一个关键字 ReentrantLock是一个类

synchronized隐式释放,运行过程中出现异常会自己释放锁

Reentrant需要手动添加锁和释放锁

synchronized修饰代码块和方法 ReentrantLock只能修饰代码块

线程通信

线程通讯指的是多个线程通过相互牵制,相互调度,即线程间的相互作用。

涉及三个方法

.wait一旦执行此方法,当前线程就进入阻塞状态,并释放同步锁对象。

.notify一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,

就唤醒优先级高的那个。

.notifyAll一旦执行此方法,就会唤醒所有被wait的线程。

.wait(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方

法中。

这三个方法必须通过为锁的对象调用

Thread:

	static int t=0;
    static Object object=new Object();//用静态确保同步锁始终是同一个对象
    @Override
    public void run(){
        while(true) {
            synchronized (object) {
                object.notify();//唤醒等待中的线程
                if(t<=20){
                    System.out.println(Thread.currentThread().getName()+":"+t);
                    t++;
                }else {
                    break;
                }
                try {
                    object.wait();//让线程等待,同时释放了锁,等待的续程不能自己醒来,必须让另一个线程唤醒
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
/*
w1:0
w2:1
w1:2
w2:3
w1:4
w2:5
w1:6
w2:7
w1:8
w2:9
w1:10
w2:11
w1:12
w2:13
w1:14
w2:15
w1:16
w2:17
w1:18
w2:19
w1:20
*/

第三种创建线程的方式:

用Callable接口重写call方法

call方法有返回值并且可以抛出异常,还可以自定义返回结果的类型<泛型>

CaTask:

public class CaTask<T> implements Callable<T> {

    @Override
    public T call() throws Exception {
        Integer i=0;
        for (int j = 0; j < 10; j++) {
            i+=j;
        }
        return (T)i;
    }
}

CaTest:

CaTask<Integer> caTask=new CaTask<>();
        //需要借助FutureTask类,获取返回结果
        //接收任务
        FutureTask<Integer> futureTask=new FutureTask<>(caTask);//中转站
        Thread thread=new Thread(futureTask);
        thread.start();
        try {
            System.out.println(Integer.valueOf(futureTask.get()));//返回值用T.valueOf(futureTask.get())获得
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
//45

你可能感兴趣的:(java,python,开发语言)