Java基础-多线程总结

线程常用方法

方法

说明

public static void sleep(long millis)

当前线程主动休眠millis毫秒。

public static void yield()

当前线程主动放弃时间片,回到就绪状态,竞争下一次时间片。

public final void join()

允许其他线程加入到当前线程中

Thread.setDaemon(true)

守护进程,Thread(线程名),必须在线程启动之前调用

Thread.setPriority(int a)

线程优先级,a为1-10

方法就不和大家演示了

如何使用多线程
  • 继承Thread类
class MyThread extends Thread{

    @Override
    public void run() {
        //编写线程中的操作

    }
}
  • 实现Runnable接口
class MyRunnable implements Runnable{
	public void run(){
		//多线程处理的逻辑代码
	}
}

推荐使用Runnable

1.因为java中单继承,继承了Thread类就无法在继承其他的类了,使用Runnable接口更为灵活一点;

2.另外继承代表强关联,实现稍微弱一点;

执行线程的是start方法

案例

使用子线程打印 0-9

  • 继承Thread
//继承Thread
public class ThreadDemo {
    public static void main(String[] args) {

        MyThread myThread = new MyThread();
        myThread.start();//开启子线程
        System.out.println("主线程结束");

    }

}
class MyThread extends Thread{

    @Override
    public void run() {
        for (int i=0;i<10;i++){
            //Thread.currentThread().getName(),当前线程的名字
            System.out.println(Thread.currentThread().getName()+"---"+i);
        }
        System.out.println("子线程结束");
    }
}
  • 实现Runnable
//实现Runnable
public class RunnableDemo {
    public static void main(String[] args) {
       MyRunnable myRunnable = new MyRunnable();
       Thread thread = new Thread(myRunnable);
       thread.start();
        System.out.println("主线程结束");

    }

}
class MyRunnable implements Runnable {

    @Override
    public void run() {
        for (int i=0;i<10;i++){
            //Thread.currentThread().getName(),当前线程的名字
            System.out.println(Thread.currentThread().getName()+"---"+i);
        }
        System.out.println("子线程结束");
    }
}
  • 其他
public class RunnableDemo1 {
    public static void main(String[] args) {
		//使用匿名内部类创建线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                //执行代码
            }
        }).start();

    }

}

两者运行结果

java 多线程的用处 java多线程总结_System

线程安全问题
  • 什么是线程不安全
  • 当多线程并发访问临界资源时,如果破坏原子操作,可能会造成数据不一致
  • 临界资源:共享资源(同一对象),一次仅允许一个线程,才可以保证其正确性。
  • 原子操作:不可分割的多步操作,被视作一个整体其顺序不可打乱或缺省
  • 案例:车站有四个窗口共卖100张票
public class Sell2 {

    public static void main(String[] args) {
        /*车站四个窗口共卖100张票*/
        MyRunnable2 runnable = new MyRunnable2();
        Thread thread1 = new Thread(runnable,"窗口一");//第二个参数,是线程的名字
        Thread thread2 = new Thread(runnable,"窗口二");//第二个参数,是线程的名字
        Thread thread3 = new Thread(runnable,"窗口三");//第二个参数,是线程的名字
        Thread thread4 = new Thread(runnable,"窗口四");//第二个参数,是线程的名字

        //开启线程
        thread1.start();
        thread2.start();
        thread3.start();
        thread4.start();

    }

}

class MyRunnable2 implements Runnable {
    int size = 100;

    @Override
    public void run() {


        while (size>0){
            try {
                Thread.sleep(100);//让线程睡眠100毫秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            size--;
            System.out.println(Thread.currentThread().getName() + "已售出一张票,剩余票数为" + size);
        }

    }

}

运行结果

java 多线程的用处 java多线程总结_java_02

  • 案例分析

假设窗口一拿到了最后一张票时,还没来得及size–,其他窗口已经进入了while(size>0)循环,所以导致上述问题

解决线程安全问题
  • 使用同步代码块
  • synchronized(临界资源对象){//原子操作}//(给临界资源加锁)
public class Sell2 {

    public static void main(String[] args) {
        /*车站四个窗口共卖100张票*/
        MyRunnable2 runnable = new MyRunnable2();
        Thread thread1 = new Thread(runnable,"窗口一");//第二个参数,是线程的名字
        Thread thread2 = new Thread(runnable,"窗口二");//第二个参数,是线程的名字
        Thread thread3 = new Thread(runnable,"窗口三");//第二个参数,是线程的名字
        Thread thread4 = new Thread(runnable,"窗口四");//第二个参数,是线程的名字

        //开启线程
        thread1.start();
        thread2.start();
        thread3.start();
        thread4.start();


    }

}

class MyRunnable2 implements Runnable {
    int size = 100;

    @Override
    public void run() {


        while (size>0){
            try {
                Thread.sleep(100);//让线程睡眠100毫秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (this){

                if(size>0){
                    size--;
                    System.out.println(Thread.currentThread().getName() + "已售出一张票,剩余票数为" + size);
                }
            }

        }

    }

}

确保size>0 和 size–,是一个原子操作

  • 使用同步方法
  • synchronized 返回值类型 方法名称(形参列表){//原子操作}//对当前对象(this)加锁
public class Sell {

    public static void main(String[] args) {
        /*车站四个窗口共卖100张票*/
        MyRunnable runnable = new MyRunnable();
        Thread thread1 = new Thread(runnable,"窗口一");//第二个参数,是线程的名字
        Thread thread2 = new Thread(runnable,"窗口二");//第二个参数,是线程的名字
        Thread thread3 = new Thread(runnable,"窗口三");//第二个参数,是线程的名字
        Thread thread4 = new Thread(runnable,"窗口四");//第二个参数,是线程的名字

        //开启线程
        thread1.start();
        thread2.start();
        thread3.start();
        thread4.start();


    }

}
class MyRunnable implements Runnable {
    int size = 100;

    @Override
    public void run() {


        while (size>0){
            try {
                Thread.sleep(100);//让线程睡眠100毫秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            sell();
        }

    }

    private synchronized void sell() {

        if(size>0){
            size--;
            System.out.println(Thread.currentThread().getName() + "已售出一张票,剩余票数为" + size);
        }
    }


}

不能 将while放入synchronized 修饰的方法里面,会导致只有一个线程工作

  • 使用Lock

Lock没有自动获取锁和自动释放锁概念

使用void lock();//获取锁 void unlock();//释放锁

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Sell1 {

    public static void main(String[] args) {
        /*车站四个窗口共卖100张票*/
        MyRunnable1 runnable = new MyRunnable1();
        Thread thread1 = new Thread(runnable,"窗口一");//第二个参数,是线程的名字
        Thread thread2 = new Thread(runnable,"窗口二");//第二个参数,是线程的名字
        Thread thread3 = new Thread(runnable,"窗口三");//第二个参数,是线程的名字
        Thread thread4 = new Thread(runnable,"窗口四");//第二个参数,是线程的名字

        //开启线程
        thread1.start();
        thread2.start();
        thread3.start();
        thread4.start();


    }

}

class MyRunnable1 implements Runnable {
    Lock lock = new ReentrantLock();
    int size = 100;


    @Override
    public void run() {


        while (size>0){
            try {
                Thread.sleep(100);//让线程睡眠100毫秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            lock.lock();//获取锁
            if(size>0){
                size--;
                System.out.println(Thread.currentThread().getName() + "已售出一张票,剩余票数为" + size);
            }
            lock.unlock();//释放锁
        }

    }
}
  • Lock常见的方法

方法

说明

void lock();

获取锁,锁被占用,则等待 会阻塞

boolean tryLock();

尝试获取锁,成功返回true,失败false,不阻塞

void unlock();

释放锁

  • Lock与synchronized比较

synchronized

lock

实现

jvm实现

java代码实现

释放锁的时机

1.加锁的代码块执行完毕

2.异常

finally中释放锁

线程是否一直等待

一直等待

尝试获取锁,非一直等待

类型

可重入,不可中断,非公平

可重入,可中断,可公平

方式

悲观锁

悲观锁 底层使用了cas

  • 补充
    什么是重入锁

重入锁,就是指的是以线程为单位,当一个线程获取对象锁之后,这个线程可以再次获取本对象上的锁,而其他的线程是不可以的。 (防止死锁)

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class RepetitionLock {

   public static void main(String[] args) {
       Lock lock = new ReentrantLock();
       new Thread(new Runnable() {
           @Override
           public void run() {
               lock.lock();//获取锁
               System.out.println("第一此获取锁");
                   lock.lock();//再次获取同一个锁对象
                   System.out.println("第二此获取锁");
                   lock.unlock();//释放锁
                   System.out.println("释放第二次获取的锁");
               lock.unlock();//释放锁
               System.out.println("释放第一次获取的锁");

           }
       }).start();

   }
}

死锁

一个简单的死锁案例

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class MyDeadlock {
    public static void main(String[] args) {
        Lock leftLock = new ReentrantLock();//左锁
        Lock rightLock = new ReentrantLock();//右锁

        new Thread(new Runnable() {
            @Override
            public void run() {
                leftLock.lock();//获取左锁
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"获取到了左锁");
                rightLock.lock();//获取右锁
                System.out.println(Thread.currentThread().getName()+"获取到了右锁");
                rightLock.unlock();
                leftLock.unlock();
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                rightLock.lock();//获取右锁
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"获取到了右锁");
                leftLock.lock();//获取左锁
                System.out.println(Thread.currentThread().getName()+"获取到了左锁");
                leftLock.unlock();
                rightLock.unlock();

            }
        }).start();

    }
}

运行结果:一直在运行

java 多线程的用处 java多线程总结_System_03



Lock 在这里知识简单的说了一下,大家要是对Lock感兴趣的话可以在多查查这方面的资料