JAVA笔记——多线程
转载注明出处:https://blog.csdn.net/qq_40270579/article/details/81381656
多线程
线程
进程中一个负责程序执行的控制单元(执行路径)
一个进程中可以有多个执行路径,称之为多线程。
一个进程中至少要有一个线程。
开启多个线程是为了同时运行多部分代码。
每一个线程都有自己运行的内容。这个内容可以成为线程要执行的任务。
多线程好处:解决了多部分同时运行的问题
多线程的弊端:线程太多回收效率的降低
JVM启动时就启动了多个线程,至少有两个线程可以分析出来
1、执行mani函数的线程(栈内存)
该线程的任务代码都定义在main函数中
2、负责垃圾回收的线程(堆内存)
线程调用是随机的
可以通过Thread的getName获取线程的名称
public static Thread currentThread()
返回对当前正在执行的线程对象的引用
主线程的名称:main
其他线程名称:Thread-编号(从0开始)
如何创建线程
方式一:继承Thread类
步骤:
1、定义一个类继承于Thread类
2、覆盖Thread类中的run方法
3、直接创建对象Thread的子类对象创建线程
4、调用start方法开启线程并调用任务的run方法执行
举例
package com.thread;
/**
* 通过创建Thread的子类创建线程
* @author Administrator
*
*/
public class ThreadDemo1 extends Thread {
private String name;
ThreadDemo1(String name){
this.name = name;
}
public void run() {
for(int x = 0;x<10;x++) {
System.out.println("name = "+this.name+"..."+" x = "+x+"..."+Thread.currentThread().getName());
}
}
public static void main(String[] args) {
ThreadDemo1 d1 = new ThreadDemo1("狗子");
ThreadDemo1 d2 = new ThreadDemo1("haha");
d1.start();
d2.start();
System.out.println("over"+"..."+Thread.currentThread().getName());
}
}
//调用run和调用start的区别
调用run:按照对象在main主线程中定义的顺序调用
调用start:按照线程执行
方式二、实现Runable接口
步骤:
1、定义类实现Runnable接口
2、覆盖接口中的 run方法,将线程任务代码封装到run方法中
3、通过Thread类创建线程对象,并将Runnable接口的子类对象作为Thread类的构造函数的参数进行传递。
为什么?因为线程的任务都封装在Runnable接口子类的对象的run方法中
所以要在线程对象创建时就必须明确要运行的任务
4、调用线程对象的start方法开启线程
Thread 内部部分结构
class Thread{
private Runnable r;
Thread(){};
Thread(Runnable r){
this.r = r;
}
public void run(){
if(r!=null)
r.run();
}
public void start(){
run();
}
}
Runnabe接口的好处:
1、将线程的任务从线程的子类中分离出来,进行单独的封装,按照面向对象的思想将任务的封装成对象
2、避免了JAVA单继承的局限性
所以,创建线程第二种方式较为常用
举例:
package com.thread;
/**
* 实现Runable接口创建线程
* @author Administrator
*
*/
class Demo implements Runnable{
@Override
public void run() {
show();
}
public void show() {
for(int x = 0;x<10;x++) {
System.out.println(" x = "+x+"..."+Thread.currentThread().getName());
}
}
}
public class ThreadDemo2 {
public static void main(String[] args) {
Demo d = new Demo();
Thread t1 = new Thread(d);
Thread t2 = new Thread(d);
t1.start();
t2.start();
}
}
线程的四种状态
被创建:start()
运行:具备执行资格,同时具备执行权;
冻结:sleep(time),wait()—notify()
唤醒:线程释放了执行权,同时释放执行资格;
临时阻塞状态:线程具备cpu的执行资格,没有cpu的执行权;
消亡:stop()
例子:
package com.demos;
/**
* 卖票
* @author Administrator
*
*/
class Ticket implements Runnable{
//票数100
private int num = 100;
public void sale() {
while(true) {
if(num>0)
{
System.out.println(Thread.currentThread().getName()+"..."+"num = "+num--);
}
}
}
public void run() {
sale();
}
}
public class TicketDemo {
public static void main(String[] args) {
Ticket t = new Ticket(); //创建线程对象
Thread t1 = new Thread(t); //窗口一>>线程1
Thread t2 = new Thread(t); //窗口二>>线程2
Thread t3 = new Thread(t); //窗口三>>线程3
t1.start();
t2.start();
t3.start();
}
}
存在的问题:存在线程安全问题
线程安全问题产生的原因:
1、多个线程在操作共享数据
2、操作共享数据的线程的线程代码有多条
当一个线程在执行操作共享数据的多条代码过程中,其他线程参与了运算就会导致线程安全问题的产生
解决思路:
将多条操作共享数据的线程封装起来,当右线程在执行这些代码的时候,
其他线程不可以参与运算。必须要当前线程把这些代码执行完毕,其他线程参与运行
在JAVA中,用同步代码块就可以解决这个问题
同步代码块格式:
synchronized(对象){
需要被同步的代码;
}
对象锁:控制线程
同步
同步的好处:解决了线程安全性问题
同步的弊端:相对降低了效率,因为同步外的线程都会判断同步锁
同步的前提:同步中必须右多个线程并使用同一个锁
修改后:
package com.demos;
/**
* 卖票
* @author Administrator
*
*/
class Ticket implements Runnable{
//票数100
private int num = 100;
Object obj = new Object();
public void sale() {
while(true) {
synchronized(obj) {
if(num>0)
{
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"..."+"num = "+num--);
}
}
}
}
public void run() {
sale();
}
}
public class TicketDemo {
public static void main(String[] args) {
Ticket t = new Ticket(); //创建线程对象
Thread t1 = new Thread(t); //窗口一>>线程1
Thread t2 = new Thread(t); //窗口二>>线程2
Thread t3 = new Thread(t); //窗口三>>线程3
t1.start();
t2.start();
t3.start();
}
}
同步函数:public synchronized void add(int num){}
同步函数的使用的锁是this
同步函数和同步代码块的区别:同步函数的使用的锁是固定的this
建议使用同步代码块
静态的同步函数使用的锁是该函数所属的字节码文件对象。可以用getClass方法获取,也可以用当前类名.class表示
举例:
package com.demos;
/**
* 需求:储户,两个,每个存每次存100,共存三次
* @author Administrator
*
*/
class Bank{
private int sum;
//private Object obj = new Object();
//同步函数synchronized
public synchronized void add(int num) {
//synchronized(obj) {
sum = sum + num;
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"..."+"sum = "+sum);
//}
}
}
class Add implements Runnable{
private Bank b = new Bank();
@Override
public void run() {
for(int i = 0;i<3;i++)
b.add(100);
}
}
public class BankDemo {
public static void main(String[] args) {
Add a = new Add();
Thread t1 = new Thread(a);
Thread t2 = new Thread(a);
t1.start();
t2.start();
}
}
多线程下的单例模式
//饿汉式
class Signle{
private static final Signle s = new Signle();
private Signle(){};
public static Signle getInstance(){
return s;
}
}
//懒汉式
class Signle{
private static final Signle s = null;
private Signle(){};
public static Signle getInstance(){
if(s==null){
synchronized(Signle.class){
if(s==null)
s = new Signle();
return s;
}
}
}
}
Class SignleDemo{
public static void main(String []args){
}
}
死锁
1、同步块里嵌套竞争锁
package com.thread;
/**
* 死锁
* @author Administrator
*
*/
class Test implements Runnable{
private boolean flag;
Test(boolean flag){
this.flag = flag;
}
@Override
public void run() {
if(flag) {
synchronized(MyLock.locka) {
System.out.println(Thread.currentThread().getName()+"..."+"获取A锁");
System.out.println(Thread.currentThread().getName()+"..."+"等待获取B锁");
synchronized(MyLock.lockb) {
System.out.println(Thread.currentThread().getName()+"..."+"获取B锁");
}
}
}
else {
synchronized(MyLock.lockb) {
System.out.println(Thread.currentThread().getName()+"..."+"获取B锁");
System.out.println(Thread.currentThread().getName()+"..."+"等待获取A锁");
synchronized(MyLock.locka) {
System.out.println(Thread.currentThread().getName()+"..."+"获取A锁");
}
}
}
}
}
class MyLock {
public static final Object locka = new Object();
public static final Object lockb = new Object();
}
public class DeadLockDemo {
public static void main(String[] args) {
//创建两个线程对象
Test a = new Test(true);
Test b = new Test(false);
Thread t1 = new Thread(a);
Thread t2 = new Thread(b);
t1.start();
t2.start();
}
}
Thread-0得到A锁,进入死锁,需要等待B锁释放;
Thread-1得到B锁,进入死锁,需要等待A锁释放.
和谐得的情况:
Thread-0...获取A锁
Thread-0...等待获取B锁
Thread-0...获取B锁
Thread-1...获取B锁
Thread-1...等待获取A锁
Thread-1...获取A锁
线程通信
1:将资源封装成对象。
2:将线程执行的任务(任务其实就是run方法。)也封装成对象。
等待唤醒机制
涉及的方法:
1、wait(): 让线程处于冻结状态,被wait()的线程会被存储到线程池中
2、notify():唤醒线程池中的一个线程(任意)
3、notiftAll():唤醒线程池中的所有线程
这些方法都必须定义在同步中
因为这些方法是用于操作线程状态的方法,必须要明确到底是操作的哪个锁上的线程
为什么操作线程的方法wait notify notifyAll定义在Object类中?
因为这些方法是监视器的方法,监视器其实就是锁,锁可以是任意的对象,任意的对象调用的方式一定定义在Object类中
例子:
package com.thread;
/**
* 等待唤醒机制
* @author Administrator
*
*/
class Resourse {
private String name;
private String sex;
private boolean flag = false;
public synchronized void set(String name,String sex) {
if(flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.name = name;
this.sex = sex;
flag = true;
this.notify();
}
public synchronized void out() {
if(!flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(name+"..."+sex);
flag = false;
this.notify();
}
}
//输入
class Input implements Runnable{
Resourse r = new Resourse();
Input(Resourse r){
this.r = r;
}
@Override
public void run() {
int x = 0;
while(true) {
if(x == 0) {
r.set("小米", "男");
}
else {
r.set("洛洛", "女");
}
x=(x+1)%2;
}
}
}
//输出
class Output implements Runnable{
Resourse r = new Resourse();
Output(Resourse r){
this.r = r;
}
@Override
public void run() {
while(true) {
r.out();
}
}
}
public class ResouseDemo {
public static void main(String[] args) {
//创建资源
Resourse r = new Resourse();
//创建任务
Input in = new Input(r);
Output out = new Output(r);
//创建线程
Thread t1 = new Thread(in);
Thread t2 = new Thread(out);
t1.start();
t2.start();
}
}
生产者、消费者
单一生产者、消费者
package com.thread;
/**
* 等待唤醒机制:生产者、消费者
* @author Administrator
*
*/
class Resource{
private String name;
private static int count = 1;
private boolean flag = false;
public synchronized void set(String name) {
if(flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.name = name + count;
count++;
System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
flag = true;
this.notify();
}
public synchronized void out() {
if(!flag) {
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+">>>消费者>>>"+this.name);
flag = false;
this.notify();
}
}
//生产者
class Producer implements Runnable{
private Resource r;
Producer(Resource r) {
this.r = r;
}
@Override
public void run() {
while(true) {
r.set("烤鸭");
}
}
}
//消费者
class Consumer implements Runnable{
private Resource r;
Consumer(Resource r) {
this.r = r;
}
@Override
public void run() {
while(true) {
r.out();
}
}
}
public class ProduerConsumerDemo {
public static void main(String[] args) {
//创建资源
Resource r = new Resource();
//创建任务
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
//创建线程
Thread t1 = new Thread(pro);
Thread t2 = new Thread(con);
t1.start();
t2.start();
}
}
多生产、多消费问题(死锁)
if判断flag改为while判断实现多生产、多消费导致没有唤醒的死锁
解决方法:将notify();改为notifyAll();
代码修改:
package com.thread;
/**
* 等待唤醒机制:单一生产者、消费者
* @author Administrator
* 改为多生产者、消费者:
* if(flag)改为while(flag)
* this.notify();改为this.notifyAll();
*/
class Resource{
private String name;
private static int count = 1;
private boolean flag = false;
public synchronized void set(String name) {
while(flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.name = name + count;
count++;
System.out.println(Thread.currentThread().getName()+"...生产者...生产"+this.name);
flag = true;
this.notifyAll();
}
public synchronized void out() {
while(!flag) {
try {
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+">>>消费者>>>消费"+this.name);
flag = false;
this.notifyAll();
}
}
//生产者
class Producer implements Runnable{
private Resource r;
Producer(Resource r) {
this.r = r;
}
@Override
public void run() {
while(true) {
r.set("烤鸭");
}
}
}
//消费者
class Consumer implements Runnable{
private Resource r;
Consumer(Resource r) {
this.r = r;
}
@Override
public void run() {
while(true) {
r.out();
}
}
}
public class ProduerConsumerDemo {
public static void main(String[] args) {
//创建资源
Resource r = new Resource();
//创建任务
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
//创建线程
Thread t0 = new Thread(pro);
Thread t1 = new Thread(pro);
Thread t2 = new Thread(con);
Thread t3 = new Thread(con);
t0.start();
t1.start();
t2.start();
t3.start();
}
}
多生产、多消费问题JDK新特征
synchronized 同步代码块对锁的操作是隐式的
JDK1.5后定义了Lock接口,存在于java.util.concurrent.locks
Lock实现提供比使用synchronized方法和语句可以获得的更广泛的锁定操作
Lock接口:替代了同步函数或同步代码块。将同步的隐式锁操作变成显示锁的操作。
同时更加灵活,可以在一个锁里加多个监视器
Condition接口:替代了Object中的wait notify notiftAll方法
将这些监视器方法单独进行封装,变成Condition监视器的对象,可以任意锁进行组合
提供了await();signal();signalAll();方法
JDK API提供的范例
class BoundedBuffer {
final Lock lock = new ReentrantLock();
final Condition notFull = lock.newCondition();
final Condition notEmpty = lock.newCondition();
final Object[] items = new Object[100];
int putptr, takeptr, count;
public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length)
notFull.await();
items[putptr] = x;
if (++putptr == items.length) putptr = 0; //达到生产数量length后从0开始继续生产
++count;
notEmpty.signal();
} finally {
lock.unlock();
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0)
notEmpty.await();
Object x = items[takeptr];
if (++takeptr == items.length) takeptr = 0;//达到生产数量大小length后从0开始继续消费
--count;
notFull.signal();
return x;
} finally {
lock.unlock();
}
}
}
生产者、消费者实例修改
package com.thread;
import java.util.concurrent.locks.*
/**
* 等待唤醒机制:生产者、消费者JDK1.5后解决办法
* @author Administrator
*/
class Resource{
private String name;
private static int count = 1;
private boolean flag = false;
//创建锁对象lock
Lock lock = new ReentrantLock();
//通过已有的锁获取该锁上的监视器对象
//Condition con = lock.newCondition();
//通过已有的锁获取两组监视器,一组监视生产者,一组监视消费者
Condition producer_con = lock.newCondition();
Condition consumer_con = lock.newCondition();
public void set(String name) {
lock.lock();
try{
while(flag) {
try {
//this.wait();
//con.await();
producer_con.signal();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.name = name + count;
count++;
System.out.println(Thread.currentThread().getName()+"...生产者...生产"+this.name);
flag = true;
//this.notifyAll();
//con.signalAll();
consumer_con.signal();
}
finally{
lock.unlock();
}
}
public synchronized void out() {
lock.lock();
try{
while(!flag) {
try {
//this.wait();
//con.await();
consumer_con.await();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+">>>消费者>>>消费"+this.name);
flag = false;
//this.notifyAll();
//con.signalAll();
producer_con.signal();
}
finally{
lock.unlock();
}
}
}
//生产者
class Producer implements Runnable{
private Resource r;
Producer(Resource r) {
this.r = r;
}
@Override
public void run() {
while(true) {
r.set("烤鸭");
}
}
}
//消费者
class Consumer implements Runnable{
private Resource r;
Consumer(Resource r) {
this.r = r;
}
@Override
public void run() {
while(true) {
r.out();
}
}
}
public class ProduerConsumerDemo {
public static void main(String[] args) {
//创建资源
Resource r = new Resource();
//创建任务
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
//创建线程
Thread t0 = new Thread(pro);
Thread t1 = new Thread(pro);
Thread t2 = new Thread(con);
Thread t3 = new Thread(con);
t0.start();
t1.start();
t2.start();
t3.start();
}
}
wait 和 sleep(都能使线程处于冻结状态) 的区别
1、wait可以指定时间
sleep必须指定时间
2、在同步中时,对CPU的执行权和锁的处理不同
wait:释放执行权,释放锁
sleep:释放执行权,不释放锁
停止线程
1、stop方法,已过时,该方法具有固有的不安全性
2、run方法结束
怎么控制线程的任务结束呢?
任务中都会有循环结构只要控制住循环就可以结束任务
控制循环通过定义标记来完成
如果线程处于冻结状态,无法读取标记,该如何结束?
可以使用interrupt()方法将线程从冻结状态强制恢复到运行状态中来,让线程具备CPU的执行资格
但是强制动作会发生InterruptedException异常,需要处理
线程中常见的方法
setDaemon(); 守护线程
public final void setDaemon(boolean on)将此线程标记为daemon线程或用户线程。 当运行的唯一线程都是守护进程线程时,Java虚拟机将退出。
线程启动前必须调用此方法。
参数
on - 如果 true ,将此线程标记为守护线程
异常
IllegalThreadStateException - 如果这个线程是 alive
SecurityException - 如果 checkAccess()确定当前线程不能修改此线程
join();
public final void join()
throws InterruptedException等待这个线程死亡。
调用此方法的行为方式与调用完全相同
join (0)
异常
InterruptedException - 如果任何线程中断当前线程。 当抛出此异常时,当前线程的中断状态将被清除。
t1.join(); //t1线程要申请加入进来运行。临时加入一个线程运算时可以使用join方法
toString
public String toString()返回此线程的字符串表示,包括线程的名称,优先级和线程组。
重写:
toString在 Object
结果
这个线程的字符串表示形式。
Modifier and Type Field and Description
static int MAX_PRIORITY (10)
线程可以拥有的最大优先级。
static int MIN_PRIORITY (1)
线程可以拥有的最小优先级。
static int NORM_PRIORITY (5)
分配给线程的默认优先级。
setPriority(int newPriority)
更改此线程的优先级。
yield
public static void yield()对调度程序的一个暗示,即当前线程愿意产生当前使用的处理器。 调度程序可以自由地忽略这个提示。
产量是一种启发式尝试,以改善否则会过度利用CPU的线程之间的相对进度。 其使用应与详细的分析和基准相结合,以确保其具有预期的效果。
很少使用这种方法。 它可能对调试或测试有用,可能有助于根据种族条件重现错误。 在设计并发控制结构(例如java.util.concurrent.locks包中的并行控制结构)时也可能有用。