Java多线程
课程内容的介绍
一、线程的相关概念
1.进程和线程
二、线程的实现方式
1.第一种创建方式
package com.bobo.thread;
public class ThreadDemo02 {
/**
* 线程的第一种实现方式
* 通过创建Thread类的子类来实现
* @param args
*/
public static void main(String[] args) {
System.out.println("main方法执行了1...");
// Java中的线程 本质上就是一个Thread对象
Thread t1 = new ThreadTest01();
// 启动一个新的线程
// start方法并不能直接开启一个新的线程,真正开启线程的是 CPU,
// 当CPU空闲或者分配到此任务的时候就会创建一个新的线程,然后执行run方法中的代码
t1.start();
// t1.start(); 线程不能够启动多次 IllegalThreadStateException
// 如果要创建多个线程,就创建多个Thread对象即可
Thread t2 = new ThreadTest01();
t2.start();
// t1.run(); // 这个是显示的调用Thread的run方法 并没有开启新的线程
for(int i = 0 ; i< 10 ; i++){
System.out.println("main方法的循环..."+i);
}
System.out.println("main方法执行结束了3...");
}
}
/**
* 第一个自定义的线程类
* 继承Thread父类
* 重写run方法
*/
class ThreadTest01 extends Thread{
/**
* 当线程启动的时候会执行run方法中的代码
*/
@Override
public void run() {
System.out.println("我们的第一个线程执行了2....");
for(int i = 0 ; i < 10 ; i ++){
System.out.println("子线程:"+i);
}
}
}
main方法执行了1...
我们的第一个线程执行了2....
我们的第一个线程执行了2....
子线程:0
子线程:1
子线程:2
子线程:3
子线程:4
子线程:5
子线程:6
子线程:7
子线程:8
子线程:9
子线程:0
子线程:1
子线程:2
main方法的循环...0
main方法的循环...1
main方法的循环...2
main方法的循环...3
main方法的循环...4
main方法的循环...5
main方法的循环...6
main方法的循环...7
子线程:3
子线程:4
子线程:5
子线程:6
子线程:7
子线程:8
子线程:9
main方法的循环...8
main方法的循环...9
main方法执行结束了3...
package com.bobo.thread;
import java.io.*;
public class ThreadDemo03 extends Thread{
/**
* 在主线程中打印1到100,然后创建一个子线程实现大文件的复制工作。
* @param args
*/
public static void main(String[] args) {
System.out.println("主线程开始了...");
// 创建线程对象
ThreadDemo03 t1 = new ThreadDemo03();
t1.start();
printNum(1,100);//主线程
System.out.println("主线程结束了...");
}
@Override //重写run方法,
public void run() {
long start = System.currentTimeMillis();
System.out.println("子线程开始文件复制...");
// 实现文件的复制
try {
copyFile(new File("D:/IO/TEST.png"),new File("D:/NewIO/TEST.png"));
} catch (Exception e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("子线程结束文件复制...耗时:" + (end-start));
}
/**
* 通过循环打印数字
* @param start
* @param end
*/
public static void printNum(int start , int end){
//通过循环打印数字
for (int i = start ; i <= end ; i ++){
System.out.print(i + "\t");
if(i % 5 == 0){
System.out.println();
}
}
}
/**
* 实现文件的复制操作
* @param srcFile 要复制的源文件
* @param descFile 要复制到的目标文件
*/
public static void copyFile(File srcFile,File descFile) throws Exception{
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcFile));
//通过缓冲流去读
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(descFile));
byte[] bytes = new byte[1024*1024];
int num = 0 ;
while((num = bis.read(bytes)) != -1){
bos.write(bytes,0,num);
}
bos.flush();;
bos.close();
bis.close();
}
}
package com.bobo.thread;
public class ThreadDemo04 {
/**
* 如果我们创建的线程类 在程序中我们只需要创建一个线程就不需要再使用的情况下
* 我们可以通过内部类的方式来简化操作
* @param args
*/
public static void main(String[] args) {
Thread t1 = new Thread(){
@Override //重写run方法
public void run() {
System.out.println("子线程执行了...");
}
};
t1.start();
}
}
package com.bobo.thread;
public class ThreadDemo05 {
/**
* 如果我们创建的线程类 在程序中我们只需要创建一个线程就不需要再使用的情况下
* 我们可以通过内部类的方式来简化操作
* @param args
*/
public static void main(String[] args) {
new Thread(){
@Override
public void run() {
System.out.println("子线程执行了.......");
}
}.start();
}
}
2.第二种创建方式
Thread(Runnable target)
分配一个新的 Thread对象。
package com.bobo.runable;
public class RunableDemo01 {
/**
* 线程的第二种方式
* 本质是创建Thread对象的时候传递了一个Runable接口实现
* @param args
*/
public static void main(String[] args) {
System.out.println("main执行了...");
// 创建一个新的线程 Thread对象
Runnable r1 = new RunableTest();
//有别于第一种实现方式,通过Runable接口实现的线程可以多个线程同时操作一个Runable接口对象
Thread t1 = new Thread(r1);
// 启动线程
t1.start();
// 创建一个新的线程
System.out.println("main结束了...");
}
}
/**
* 线程的第二种创建方式
* 创建一个Runable接口的实现类
*/
class RunableTest implements Runnable{ //实现一个Runnable接口
@Override
public void run() {
System.out.println("子线程执行了...");
}
}
main执行了...
main结束了...
子线程执行了...
/**
* 线程的第二种方式
* 本质是创建Thread对象的时候传递了一个Runable接口实现
* @param args
*/
public static void main(String[] args) {
System.out.println("main执行了...");
// 创建一个新的线程 Thread对象
Runnable r1 = new RunableTest();
//有别于第一种实现方式,通过Runable接口实现的线程可以多个线程同时操作一个Runable接口对象
Thread t1 = new Thread(r1);
// 启动线程
t1.start();
// 创建一个新的线程
Thread t2 = new Thread(r1);
t2.start();
System.out.println("main结束了...");
}
package com.bobo.runable;
public class RunableDemo02 {
/**
* 通过Runable接口的方式实现的线程的简写方式 内部类方式
* @param args
*/
public static void main(String[] args) {
Runnable r1 = new Runnable() {
@Override
public void run() {
System.out.println("子线程执行了..");
}
};
Thread t1 = new Thread(r1);
t1.start();
Thread t2 = new Thread(r1);
t2.start();
}
}
package com.bobo.runable;
public class RunableDemo03 {
/**
* 通过Runable接口的方式实现的线程的简写方式 内部类方式
* @param args
*/
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("子线程执行了..");
}
});
t1.start();
// 更加的简洁
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("子线程执行了..");
}
}).start();
}
}
package com.bobo.runable;
import java.io.*;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class RunableDemo04 {
/**
* 课堂案例
*
* 在主线程中打印1到100,然后创建一个子线程实现大文件的复制工作。
* @param args
*/
public static void main(String[] args) {
System.out.println("main方法执行了");
printNum(1,100);
new Thread(new Runnable() {//用接口实现
@Override
public void run() {
long start = System.currentTimeMillis();
System.out.println("子线程开始执行");
try {
RunableDemo04.copyFile(new File("d:/IO/TEST.png"),new File("d:/NewIO/TEST.png"));
}catch (Exception e){
e.printStackTrace();
}//调取异常
long end = System.currentTimeMillis();//耗时计算
System.out.println("子线程执行结束,耗时:" + (end - start));
}
}).start();
System.out.println("main方式执行结束");
}
/**
* 通过循环打印数字
* @param start
* @param end
*/
public static void printNum(int start , int end){
for (int i = start ; i <= end ; i ++){
System.out.print(i + "\t");
if(i % 5 == 0){
System.out.println();
}
}
}
/**
* 实现文件的复制操作
* @param srcFile 要复制的源文件
* @param descFile 要复制到的目标文件
*/
public static void copyFile(File srcFile, File descFile) throws Exception{
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcFile));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(descFile));
byte[] bytes = new byte[1024*1024];
int num = 0 ;
while((num = bis.read(bytes)) != -1){
bos.write(bytes,0,num);
}
bos.flush();;
bos.close();
bis.close();
}
}
3.第三种创建方式
package com.bobo.callable;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class CallableDemo01 {
/**
* 创建线程的第三种实现方式:
* Callable方式
*/
public static void main(String[] args) throws Exception {
// 创建一个Callable实例
Callable<Integer> callable = new MyCallable();
FutureTask<Integer> futureTask = new FutureTask<>(callable);
// 获取一个线程 肯定是要先创建一个Thread对象 futureTask本质上是Runable接口的实现
Thread t1 = new Thread(futureTask);
System.out.println("main方法start....");
t1.start(); // 本质还是执行的 Runable中的run方法,只是 run方法调用了call方法罢了
// 获取返回的结果
System.out.println(futureTask.get()); // 获取开启的线程执行完成后返回的结果
System.out.println("main方法end ....");
}
}
/**
* 创建Callable的实现类
* 我们需要指定Callable的泛型,这个泛型是返回结果的类型
*/
class MyCallable implements Callable<Integer>{
/**
* 线程自动后会执行的方法
* @return
* @throws Exception
*/
@Override
public Integer call() throws Exception {
int sum = 0;
for(int i = 1 ; i <= 100 ; i ++){
sum += i;
}
return sum;
}
}
main方法start....
5050
main方法end ....
三、线程中常用的方法
1.start方法
void start()
// 导致此线程开始执行; Java虚拟机调用此线程的run方法。
2.run方法
void run()
// 如果这个线程使用单独的Runnable运行对象构造,则调用该Runnable对象的run方法; 否则,此方法不执行任何操作并返回。
3.getName方法
String getName()
返回此线程的名称。
package com.bobo.fun;
public class ThreadFunDemo01 {
/**
* 线程中的常用的方法
* @param args
*/
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName() + " main执行了");
Runnable runnable = new MyRunable();
// 创建并启动多个线程
new Thread(runnable).start();
new Thread(runnable).start();
new Thread(runnable).start();
new Thread(runnable).start();
new Thread(runnable).start();
new Thread(runnable).start();
new Thread(runnable).start();
}
}
class MyRunable implements Runnable{
@Override
public void run() {
// Thread.currentThread() 获取当前线程对象
System.out.println(Thread.currentThread().getName() + " 执行了...");
}
}
main main执行了
Thread-1 执行了...
Thread-0 执行了...
Thread-6 执行了...
Thread-2 执行了...
Thread-3 执行了...
Thread-5 执行了...
Thread-4 执行了...
package com.bobo.fun;
public class ThreadFunDemo02{
/**
* 线程中的常用的方法
* @param args
*/
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName() + " main执行了");
// 创建并启动多个线程
Thread t1 = new MyThread01("线程A");
t1.start();
Thread t2 = new MyThread01("线程B");
t2.start();
Thread t3 = new MyThread01("线程C");
t3.start();
}
}
class MyThread01 extends Thread{
public MyThread01(String threadName){
// 指定线程的名称
super(threadName);
}
@Override
public void run() {
System.out.println(this.getName() + " 执行了...");
}
}
main main执行了
线程A 执行了...
线程C 执行了...
线程B 执行了...
package com.bobo.fun;
public class ThreadFunDemo03 {
public static void main(String[] args) {
Runnable runnable = new MyRunable01();
// 重命名 线程的名称
Thread t1 = new Thread(runnable,"线程I");
t1.start();
Thread t2 = new Thread(runnable,"线程II");
t2.start();
Thread t3 = new Thread(runnable,"线程III");
t3.start();
}
}
class MyRunable01 implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " 执行了...");
}
}
线程II 执行了...
线程III 执行了...
线程I 执行了...
4.优先级
/**
* 最小的优先级是 1
*/
public final static int MIN_PRIORITY = 1;
/**
* 默认的优先级都是5
*/
public final static int NORM_PRIORITY = 5;
/**
* 最大的优先级是10
*/
public final static int MAX_PRIORITY = 10;
package com.bobo.fun;
public class ThreadFunDemo05 {
/**
* 线程的优先级
* @param args
*/
public static void main(String[] args) {
Runnable runnable = new MyRunable02();
for(int i = 1 ; i <= 10 ; i ++){
Thread t1 = new Thread(runnable,"" + i);
t1.setPriority(i); // 设置优先级大小
t1.start();
}
}
}
class MyRunable02 implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+" 执行了 优先级:" + Thread.currentThread().getPriority());
}
}
9 执行了 优先级:9
6 执行了 优先级:6
3 执行了 优先级:3
5 执行了 优先级:5
4 执行了 优先级:4
10 执行了 优先级:10
8 执行了 优先级:8
7 执行了 优先级:7
2 执行了 优先级:2
1 执行了 优先级:1
5.sleep方法
static void sleep(long millis)
// 使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行),具体取决于系统定时器和调度程序的精度和准确性。
package com.bobo.fun;
public class ThreadFunDemo06 {
/**
* sleep 方法
* 休眠
* @param args
*/
public static void main(String[] args) throws Exception{
System.out.println("main ... start");
Thread.sleep(1000);
Runnable runnable = new MyRunable03();
new Thread(runnable).start();
Thread.sleep(1000);
System.out.println("main .... end ");
}
}
class MyRunable03 implements Runnable{
@Override
public void run() {
for (int i = 1 ; i < 50 ; i ++){
// 每循环一次 休眠 100毫秒
try {
// 将当前线程休眠指定的时间
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " :" + i);
}
}
}
package com.bobo.fundemo;
public class FunDemo01 {
/**
* 练习1:设计一个线程类:创建3个子线程,每个线程分别打印数字,分别睡100,200,300
* @param args
*/
public static void main(String[] args) {
new Thread(new MyRunable01(100),"A").start();
new Thread(new MyRunable01(200),"B").start();
new Thread(new MyRunable01(300),"C").start();
}
}
class MyRunable01 implements Runnable{
private int sleepTime;
public MyRunable01(int sleepTime){
this.sleepTime = sleepTime;
}
@Override
public void run() {
for(int i = 0 ; i < 10; i ++){
System.out.println(Thread.currentThread().getName() + " :" + i);
// 休眠指定的时间
try {
Thread.sleep(sleepTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
B :0
A :0
C :0
A :1
B :1
A :2
C :1
A :3
B :2
A :4
A :5
C :2
B :3
A :6
A :7
B :4
A :8
C :3
A :9
B :5
B :6
C :4
B :7
C :5
B :8
B :9
C :6
C :7
C :8
C :9
package com.bobo.fundemo;
import java.util.Date;
public class FunDemo02 {
/**
* 练习2:设计一个线程:运行10秒后被终止(掌握线程的终止方法)
* @param args
*/
public static void main(String[] args) throws Exception{
MyRunable02 runnable = new MyRunable02();
new Thread(runnable).start();
Thread.sleep(10000); // 主线程休眠10秒钟
runnable.flag = false;
System.out.println("main、 end ...");
}
}
class MyRunable02 implements Runnable{
boolean flag = true;
@Override
public void run() {
while(flag){
try {
Thread.sleep(1000);
System.out.println(new Date());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + " 执行完成");
}
}
package com.bobo.fundemo;
public class FunDemo03 {
/**
* 练习3:设计一个线程类,启动2个子线程,一个子线程每打印10个数字睡眠,另一个线程每打印20个睡眠。。
* @param args
*/
public static void main(String[] args) {
new Thread(new MyRunableO3(),"A").start();
new Thread(new MyRunableO3(),"B").start();
}
}
class MyRunableO3 implements Runnable{
private int i = 0;
@Override
public void run() {
String name = Thread.currentThread().getName();
while (i < 1000){
if("A".equals(name)){
if(i % 10==0 && i != 0){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}else if("B".equals(name)){
if(i%20 ==0 && i!= 0){
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
System.out.println(name + "\t" + i++);
}
}
}
6.isAlive
package com.bobo.fundemo;
public class FunDemo04 {
/**
* isAlive方法
* @param args
*/
public static void main(String[] args) {
System.out.println("main start ...");
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " .... ");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
System.out.println("线程的状态:"+t1.isAlive());
t1.start();
System.out.println("线程的状态:"+t1.isAlive());
System.out.println("main end ...");
}
}
main start ...
线程的状态:false
线程的状态:true
main end ...
Thread-0 ....
7.join
package com.bobo.fundemo;
public class FunDemo05 {
/**
* 线程的合并
* join方法
* @param args
*/
public static void main(String[] args) {
System.out.println("main start ...");
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for(int i = 0 ; i < 10; i++){
System.out.println(Thread.currentThread().getName() + " 子线程执行了...");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
t1.start();
try {
t1.join(); // 线程的合并,和主线程合并 相当于我们直接调用了run方法
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("main end ...");
}
}
main start ...
Thread-0 子线程执行了...
Thread-0 子线程执行了...
Thread-0 子线程执行了...
Thread-0 子线程执行了...
Thread-0 子线程执行了...
Thread-0 子线程执行了...
Thread-0 子线程执行了...
Thread-0 子线程执行了...
Thread-0 子线程执行了...
Thread-0 子线程执行了...
main end ...
8.yield
package com.bobo.fundemo;
public class FuneDemo06 extends Thread{
public FuneDemo06(String threadName){
super(threadName);
}
/**
* yield方法 礼让
*
* @param args
*/
public static void main(String[] args) {
FuneDemo06 f1 = new FuneDemo06("A1");
FuneDemo06 f2 = new FuneDemo06("A2");
FuneDemo06 f3 = new FuneDemo06("A3");
f1.start();
f2.start();
f3.start();
}
@Override
public void run() {
for(int i = 0 ; i < 100; i ++){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(i%10 == 0 && i != 0){
System.out.println(Thread.currentThread().getName()+" 礼让:" + i);
Thread.currentThread().yield(); // 让出CPU
}else{
System.out.println(this.getName() + ":" + i);
}
}
}
}
9.wait和notify/notifyAll
四、线程的生命周期
1. 生命周期
2. 线程的中断
2.1 设置标志位
package com.bobo.fundemo;
import java.util.Date;
public class FunDemo02 {
/**
* 练习2:设计一个线程:运行10秒后被终止(掌握线程的终止方法)
* @param args
*/
public static void main(String[] args) throws Exception{
MyRunable02 runnable = new MyRunable02();
new Thread(runnable).start();
Thread.sleep(10000); // 主线程休眠10秒钟
runnable.flag = false;
System.out.println("main、 end ...");
}
}
class MyRunable02 implements Runnable{
boolean flag = true;
@Override
public void run() {
while(flag){
try {
Thread.sleep(1000);
System.out.println(new Date());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + " 执行完成");
}
}
2.2 利用中断标志位
package com.bobo.fundemo;
public class FunDemo07 extends Thread{
public static void main(String[] args) throws InterruptedException {
Thread t1 = new FunDemo07();
t1.start();
Thread.sleep(3000);
t1.interrupt(); // 中断线程 将中断标志由false修改为了true
// t1.stop(); // 直接就把线程给kill掉了
System.out.println("main .... ");
}
@Override
public void run() {
System.out.println(this.getName() + " start...");
int i = 0 ;
// Thread.interrupted() 如果没有被中断 那么是false 如果显示的执行了interrupt 方法就会修改为 true
while(!Thread.interrupted()){
//while(!Thread.currentThread().isInterrupted()){
System.out.println(this.getName() + " " + i);
i++;
}
System.out.println(this.getName()+ " end .... ");
}
}
2.3 利用InterruptedException
package com.bobo;
public class FunDemo08 extends Thread{
public static void main(String[] args) throws InterruptedException {
Thread t1 = new FunDemo08();
t1.start();
Thread.sleep(3000);
t1.interrupt(); // 中断线程 将中断标志由false修改为了true
// t1.stop(); // 直接就把线程给kill掉了
System.out.println("main .... ");
}
@Override
public void run() {
System.out.println(this.getName() + " start...");
int i = 0 ;
// Thread.interrupted() 如果没有被中断 那么是false 如果显示的执行了interrupt 方法就会修改为 true
while(!Thread.interrupted()){
//while(!Thread.currentThread().isInterrupted()){
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
// e.printStackTrace();
System.out.println("中断线程");
break;
}
System.out.println(this.getName() + " " + i);
i++;
}
System.out.println(this.getName()+ " end .... ");
}
}
五、线程数据安全问题
1. 数据安全问题分析
1.1 继承Thread方式
package com.bobo.thread1;
public class ThreadDemo01 {
/**
* 数据安全问题
* 线程的创建方式有两种
* 继承自Thread类实现
* 实现Runable接口
* @param args
*/
public static void main(String[] args) {
MyThread01 t1 = new MyThread01();
t1.setName("A");
MyThread01 t2 = new MyThread01();
t2.setName("B");
MyThread01 t3 = new MyThread01();
t3.setName("C");
t1.start();
t2.start();
t3.start();
}
}
class MyThread01 extends Thread{
private Integer count = 0;
@Override
public void run() {
//System.out.println(Thread.currentThread().getName()+ " 执行了");
// 操作count
while(count < 10){
count++;
System.out.println(Thread.currentThread().getName()+ " 执行了" + count);
}
}
}
package com.bobo.thread1;
public class ThreadDemo02{
/**
* 数据安全问题
* 线程的创建方式有两种
* 继承自Thread类实现
* 实现Runable接口
* @param args
*/
public static void main(String[] args) {
MyThread02 t1 = new MyThread02();
t1.setName("A");
MyThread02 t2 = new MyThread02();
t2.setName("B");
MyThread02 t3 = new MyThread02();
t3.setName("C");
t1.start();
t2.start();
t3.start();
}
}
class MyThread02 extends Thread{
private static Integer count = 0;
@Override
public void run() {
//System.out.println(Thread.currentThread().getName()+ " 执行了");
// 操作count
while(count < 10){
count++;
System.out.println(Thread.currentThread().getName()+ " 执行了" + count);
}
}
}
1.2 实现Runable接口
package com.bobo.thread1;
public class ThreadDemo03 {
public static void main(String[] args) {
Runnable runnable = new MyRunable();
Runnable runnable1 = new MyRunable();
Thread t1 = new Thread(runnable,"A");
Thread t2 = new Thread(runnable,"B");
Thread t3 = new Thread(runnable,"C");
// t4 线程操作的是另外一个Runable对象
Thread t4 = new Thread(runnable1,"D");
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class MyRunable implements Runnable{
private Integer count = 0;
@Override
public void run() {
while(count < 10){
count ++;
System.out.println(Thread.currentThread().getName() + " :" + count);
}
}
}
C :2
C :4
A :1
C :5
A :6
C :7
A :8
C :9
A :10
D :1
D :2
D :3
D :4
D :5
D :6
D :7
B :3
D :8
D :9
D :10
2.数据完全问题的原因
package com.bobo.thread1;
public class ThreadDemo04 {
/**
* 通过多线程模拟火车站售票的场景
* 有100张票 3个窗口售卖
* @param args
*/
public static void main(String[] args) {
// 火车票资料
Runnable runnable = new MyRunable04();
Thread t1 = new Thread(runnable,"窗口A");
Thread t2 = new Thread(runnable,"窗口B");
Thread t3 = new Thread(runnable,"窗口C");
t1.start();
t2.start();
t3.start();
}
}
class MyRunable04 implements Runnable{
// 火车票
private Integer ticket = 10;
@Override
public void run() {
while(ticket > 0){
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" 卖出了第:" + ticket-- + "张票" );
}
}
}
窗口B 卖出了第:9张票
窗口A 卖出了第:10张票
窗口C 卖出了第:10张票
窗口B 卖出了第:8张票
窗口A 卖出了第:8张票
窗口C 卖出了第:7张票
窗口A 卖出了第:6张票
窗口B 卖出了第:5张票
窗口C 卖出了第:6张票
窗口A 卖出了第:3张票
窗口B 卖出了第:4张票
窗口C 卖出了第:4张票
窗口B 卖出了第:2张票
窗口A 卖出了第:0张票
窗口C 卖出了第:1张票
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" 卖出了第:" + ticket-- + "张票" );
3.数据安全问题的解决方式
3.1 同步代码块
synchroized(obj){
// 需要同步的代码
}
package com.bobo.thread1;
public class ThreadDemo05 {
/**
* 通过多线程模拟火车站售票的场景
* 有100张票 3个窗口售卖
* @param args
*/
public static void main(String[] args) {
// 火车票资料
Runnable runnable = new MyRunable05();
Thread t1 = new Thread(runnable,"窗口A");
Thread t2 = new Thread(runnable,"窗口B");
Thread t3 = new Thread(runnable,"窗口C");
t1.start();
t2.start();
t3.start();
}
}
class MyRunable05 implements Runnable{
// 火车票
private Integer ticket = 10;
private Object obj = new Object();
@Override
public void run() {
while(ticket > 0){
// 这个锁对象必须是 共享对象
synchronized (obj){
if(ticket <= 0) continue;
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" 卖出了第:" + ticket-- + "张票" );
}
}
}
}
窗口A 卖出了第:10张票
窗口A 卖出了第:9张票
窗口A 卖出了第:8张票
窗口A 卖出了第:7张票
窗口A 卖出了第:6张票
窗口A 卖出了第:5张票
窗口A 卖出了第:4张票
窗口A 卖出了第:3张票
窗口A 卖出了第:2张票
窗口A 卖出了第:1张票
3.2 同步方法
package com.bobo.thread1;
public class ThreadDemo06 {
/**
* 通过多线程模拟火车站售票的场景
* 有100张票 3个窗口售卖
* @param args
*/
public static void main(String[] args) {
// 火车票资料
Runnable runnable = new MyRunable06();
Thread t1 = new Thread(runnable,"窗口A");
Thread t2 = new Thread(runnable,"窗口B");
Thread t3 = new Thread(runnable,"窗口C");
t1.start();
t2.start();
t3.start();
}
}
class MyRunable06 implements Runnable{
// 火车票
private Integer ticket = 10;
private Object obj = new Object();
@Override
public void run() {
while(ticket > 0){
sellTicket();
}
}
/**
* 同步方法
* 同步普通方法 锁的是 this
* 同步静态方法 锁的是 类对象
*/
public synchronized void sellTicket(){
if(ticket <= 0) return;
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" 卖出了第:" + ticket-- + "张票" );
}
}
3.3 Lock
package com.bobo.thread1;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ThreadDemo07 {
/**
* 通过多线程模拟火车站售票的场景
* 有100张票 3个窗口售卖
* @param args
*/
public static void main(String[] args) {
// 火车票资料
Runnable runnable = new MyRunable07();
Thread t1 = new Thread(runnable,"窗口A");
Thread t2 = new Thread(runnable,"窗口B");
Thread t3 = new Thread(runnable,"窗口C");
t1.start();
t2.start();
t3.start();
}
}
class MyRunable07 implements Runnable{
// 火车票
private Integer ticket = 10;
private Lock lock = new ReentrantLock();
@Override
public void run() {
while(ticket > 0){
// 加锁
lock.lock();
if(ticket <= 0) break;
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" 卖出了第:" + ticket-- + "张票" );
// 释放锁
lock.unlock();
}
}
}
J.U.C核心Abstract Queued Synchronizer AQS丝滑般流畅好了,我们来开始今天的内容,首先我们来看下AQS是什么,全称是
AbstractQueuedSynchronizer 翻译过来就是【抽象队列同步】对吧。通过名字我们也能看出这是个抽象类
而且里面定义了很多的方法。
里面这么多方法,咱们当然不是一个个去翻。里面还有很多的抽象方法,咱们还得找它的实现多麻烦对不对。所以我们换个方式来探索。
场景模拟
我们先来看下这样一个场景
在这里我们有一个能被多个线程共享操作的资源,在这个场景中应该能看出我们的数据是不安全的,因为我们并不能保证我们的操作是原子操作对吧。基于这个场景我们通过代码来看看效果。package com.example.demo; public class AtomicDemo { // 共享变量 private static int count = 0; // 操作共享变量的方法 public static void incr(){ // 为了演示效果 休眠一下子 try { Thread.sleep(1); count ++; } catch (InterruptedException e) { e.printStackTrace(); } } public static void main(String[] args) throws InterruptedException { for (int i = 0; i < 1000 ; i++) { new Thread(()->AtomicDemo.incr()).start(); } Thread.sleep(4000); System.out.println("result:" + count); } }
通过执行发现,执行的结果是一个不确定的值,但总是会小于等于1000,至于原因,是因为incr() 方法不是一个原子操作。为什么不是原子操作这个咱们今天就不深究此处了。
迎合今天的主题,我们通过Lock来解决package com.example.demo; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class AtomicDemo { // 共享变量 private static int count = 0; private static Lock lock = new ReentrantLock(); // 操作共享变量的方法 public static void incr(){ // 为了演示效果 休眠一下子 try { lock.lock(); Thread.sleep(1); count ++; } catch (InterruptedException e) { e.printStackTrace(); }finally { lock.unlock(); } } public static void main(String[] args) throws InterruptedException { for (int i = 0; i < 1000 ; i++) { new Thread(()->AtomicDemo.incr()).start(); } Thread.sleep(4000); System.out.println("result:" + count); } }
然后我们运行发现结果都是 1000了,这也就是1000个线程都去操作这个 count 变量,结果符合我们的预期了。那lock到底是怎么实现的呢?
需求分析
我们先来分析分析
这样的图片看着比较复杂,咱们简化下。
我们自己假设下,如果要你去设计这样的方法,你应该要怎么设计,他们需要实现哪些功能。
首先是lock方法,它是不是要满足这几个功能。
需求清楚了,那我们怎么设计呢?
第一个互斥怎么做,也就是多个线程只有一个线程能抢占到资源,这个时候我们可以这样设置。// 给一个共享资源 Int state = 0 ; // 0表示资源没有被占用,可以抢占 if(state == 0 ){ // 表示可以获取锁 }else{ // 表示锁被抢占 需要阻塞等待 }
然后就是没有抢占到锁的线程的存储,我们可以通过一个队列,利用FIFO来实现存储。
最后就是线程的阻塞和唤醒。大家说说有哪些阻塞线程的方式呀?1.wait/notify: 不合适,不能唤醒指定的线程
2.Sleep:休眠,类似于定时器
3.Condition:可以唤醒特定线程
4.LockSupport:
LockSupport.park():阻塞当前线程
LockSupport.unpark(Thread t):唤醒特定线程
结合今天的主题,我们选择LockSupport来实现阻塞和唤醒。
好了,到这儿我们已经猜想到了Lock中的实现逻辑,但是在探究源码之前我们还有个概念需要先和大家讲下,因为这个是我们源码中会接触到的一个,先讲了,看的时候就比较轻松了对吧。
什么是重入锁?
我们先来看看重入锁的场景代码package com.example.demo; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class AtomicDemo { // 共享变量 private static int count = 0; private static Lock lock = new ReentrantLock(); // 操作共享变量的方法 public static void incr(){ // 为了演示效果 休眠一下子 try { lock.lock(); Thread.sleep(1); count ++; // 调用了另外一个方法。 decr(); } catch (InterruptedException e) { e.printStackTrace(); }finally { lock.unlock(); } } public static void decr(){ try { // 重入锁 lock.lock(); count--; }catch(Exception e){ }finally { lock.unlock(); } } public static void main(String[] args) throws InterruptedException { for (int i = 0; i < 1000 ; i++) { new Thread(()->AtomicDemo.incr()).start(); } Thread.sleep(4000); System.out.println("result:" + count); } }
首先大家考虑这段代码会死锁吗? 大家给我个回复,我看看大家的理解的怎么样
好了,有说会 死锁的,有说不会,其实这儿是不会死锁的,而且结果就是0.为什么呢?
这个其实是锁的一个嵌套,因为这两把锁都是同一个 线程对象,我们讲共享变量的设计是当state=0;线程可以抢占到资源 state =1; 如果进去嵌套访问 共享资源,这时 state = 2 如果有多个嵌套 state会一直累加,释放资源的时候, state–,直到所有重入的锁都释放掉 state=0,那么其他线程才能继续抢占资源,说白了重入锁的设计目的就是为了防止 死锁!AQS类图
通过类图我们可以发现右车的业务应用其实内在都有相识的设计,这里我们只需要搞清楚其中的一个,其他的你自己应该就可以看懂~,好了我们就具体结合前面的案例代码,以ReentrantLock为例来介绍AQS的代码实现。
源码分析
在看源码之前先回顾下这个图,带着问题去看,会更轻松
Lock.lock()final void lock() { if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); }
这个方法逻辑比较简单,if条件成立说明 抢占锁成功并设置 当前线程为独占锁,else 表示抢占失败,acquire(1) 方法我们后面具体介绍。
compareAndSetState(0, 1):用到了CAS 是一个原子操作方法,底层是UnSafe.作用就是设置 共享操作的 state 由0到1. 如果state的值是0就修改为1。
setExclusiveOwnerThread:代码很简单,进去看一眼即可。
acquire方法
public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }
1.tryAcquire()尝试直接去获取资源,如果成功则直接返回(这里体现了非公平锁,每个线程获取锁时会尝试直接抢占加塞一次,而CLH队列中可能还有别的线程在等待);
2.addWaiter()将该线程加入等待队列的尾部,并标记为独占模式;
3.acquireQueued()使线程阻塞在等待队列中获取资源,一直获取到资源后才返回。如果在整个等待过程中被中断过,则返回true,否则返回false。如果线程在等待过程中被中断过,它是不响应的。只是获取资源后才再进行自我中断selfInterrupt(),将中断补上。
当然这里代码的作用我是提前研究过的,对于大家肯定不是很清楚,我们继续里面去看,最后大家可以回到这儿再论证。
tryAcquire(int)
再次尝试抢占锁protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); }
final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); //再次尝试抢占锁 if (c == 0) { if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } // 重入锁的情况 else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } // false 表示抢占失败 return false; }
addWaiter
将阻塞的线程添加到双向链表的结尾。private Node addWaiter(Node mode) { //以给定模式构造结点。mode有两种:EXCLUSIVE(独占)和SHARED(共享) Node node = new Node(Thread.currentThread(), mode); //尝试快速方式直接放到队尾。 Node pred = tail; if (pred != null) { node.prev = pred; if (compareAndSetTail(pred, node)) { pred.next = node; return node; } } //上一步失败则通过enq入队。 enq(node); return node; }
enq(Node)
private Node enq(final Node node) { //CAS"自旋",直到成功加入队尾 for (;;) { Node t = tail; if (t == null) { // 队列为空,创建一个空的标志结点作为head结点,并将tail也指向它。 if (compareAndSetHead(new Node())) tail = head; } else {//正常流程,放入队尾 node.prev = t; if (compareAndSetTail(t, node)) { t.next = node; return t; } } } }
第一个if语句
else语句
线程3进来会执行如下代码
那么效果图
acquireQueued(Node, int)
OK,通过tryAcquire()和addWaiter(),该线程获取资源失败,已经被放入等待队列尾部了。聪明的你立刻应该能想到该线程下一部该干什么了吧:进入等待状态休息,直到其他线程彻底释放资源后唤醒自己,自己再拿到资源,然后就可以去干自己想干的事了。没错,就是这样!是不是跟医院排队拿号有点相似~~acquireQueued()就是干这件事:在等待队列中排队拿号(中间没其它事干可以休息),直到拿到号后再返回。这个函数非常关键,还是上源码吧:final boolean acquireQueued(final Node node, int arg) { boolean failed = true;//标记是否成功拿到资源 try { boolean interrupted = false;//标记等待过程中是否被中断过 //又是一个“自旋”! for (;;) { final Node p = node.predecessor();//拿到前驱 //如果前驱是head,即该结点已成老二,那么便有资格去尝试获取资源(可能是老大释放完资源唤醒自己的,当然也可能被interrupt了)。 if (p == head && tryAcquire(arg)) { setHead(node);//拿到资源后,将head指向该结点。所以head所指的标杆结点,就是当前获取到资源的那个结点或null。 p.next = null; // setHead中node.prev已置为null,此处再将head.next置为null,就是为了方便GC回收以前的head结点。也就意味着之前拿完资源的结点出队了! failed = false; // 成功获取资源 return interrupted;//返回等待过程中是否被中断过 } //如果自己可以休息了,就通过park()进入waiting状态,直到被unpark()。如果不可中断的情况下被中断了,那么会从park()中醒过来,发现拿不到资源,从而继续进入park()等待。 if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) interrupted = true;//如果等待过程中被中断过,哪怕只有那么一次,就将interrupted标记为true } } finally { if (failed) // 如果等待过程中没有成功获取资源(如timeout,或者可中断的情况下被中断了),那么取消结点在队列中的等待。 cancelAcquire(node); } }
到这里了,我们先不急着总结acquireQueued()的函数流程,先看看shouldParkAfterFailedAcquire()和parkAndCheckInterrupt()具体干些什么。
shouldParkAfterFailedAcquire(Node, Node)private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { int ws = pred.waitStatus;//拿到前驱的状态 if (ws == Node.SIGNAL) //如果已经告诉前驱拿完号后通知自己一下,那就可以安心休息了 return true; if (ws > 0) { /* * 如果前驱放弃了,那就一直往前找,直到找到最近一个正常等待的状态,并排在它的后边。 * 注意:那些放弃的结点,由于被自己“加塞”到它们前边,它们相当于形成一个无引用链,稍后就会被保安大叔赶走了(GC回收)! */ do { node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; } else { //如果前驱正常,那就把前驱的状态设置成SIGNAL,告诉它拿完号后通知自己一下。有可能失败,人家说不定刚刚释放完呢! compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false; }
整个流程中,如果前驱结点的状态不是SIGNAL,那么自己就不能安心去休息,需要去找个安心的休息点,同时可以再尝试下看有没有机会轮到自己拿号。
parkAndCheckInterrupt()
如果线程找好安全休息点后,那就可以安心去休息了。此方法就是让线程去休息,真正进入等待状态。private final boolean parkAndCheckInterrupt() { LockSupport.park(this);//调用park()使线程进入waiting状态 return Thread.interrupted();//如果被唤醒,查看自己是不是被中断的。 }
好了,我们可以小结下了。
看了shouldParkAfterFailedAcquire()和parkAndCheckInterrupt(),现在让我们再回到acquireQueued(),总结下该函数的具体流程:
1.结点进入队尾后,检查状态,找到安全休息点;
2.调用park()进入waiting状态,等待unpark()或interrupt()唤醒自己;
3.被唤醒后,看自己是不是有资格能拿到号。如果拿到,head指向当前结点,并返回从入队到拿到号的整个过程中是否被中断过;如果没拿到,继续流程1。
最后我们再回到前面的acquire方法来总结下public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }
总结下它的流程吧
1.调用自定义同步器的tryAcquire()尝试直接去获取资源,如果成功则直接返回;
2.没成功,则addWaiter()将该线程加入等待队列的尾部,并标记为独占模式;
3.acquireQueued()使线程在等待队列中休息,有机会时(轮到自己,会被unpark())会去尝试获取资源。获取到资源后才返回。如果在整个等待过程中被中断过,则返回true,否则返回false。
4.如果线程在等待过程中被中断过,它是不响应的。只是获取资源后才再进行自我中断selfInterrupt(),将中断补上。
Lock.unlock()
好了,lock方法看完后,我们再来看下unlock方法。
release(int)
它会释放指定量的资源,如果彻底释放了(即state=0),它会唤醒等待队列里的其他线程来获取资源。这也正是unlock()的语义,当然不仅仅只限于unlock()。public final boolean release(int arg) { if (tryRelease(arg)) { Node h = head;//找到头结点 if (h != null && h.waitStatus != 0) unparkSuccessor(h);//唤醒等待队列里的下一个线程 return true; } return false; }
ryRelease(int)
此方法尝试去释放指定量的资源。下面是tryRelease()的源码:public final boolean release(int arg) { if (tryRelease(arg)) {//这里是先尝试释放一下资源,一般都可以释放成功,除了多次重入但只释放一次的情况。 Node h = head; //这里判断的是 阻塞队列是否还存在和head节点是否是tail节点,因为之前说过,队列的尾节点的waitStatus是为0的 if (h != null && h.waitStatus != 0) //到这里就说明head节点已经释放成功啦,就先去叫醒后面的直接节点去抢资源吧 unparkSuccessor(h); return true; } return false; }
private void unparkSuccessor(Node node) { //这里,node一般为当前线程所在的结点。 int ws = node.waitStatus; if (ws < 0)//置零当前线程所在的结点状态,允许失败。 compareAndSetWaitStatus(node, ws, 0); Node s = node.next;//找到下一个需要唤醒的结点s if (s == null || s.waitStatus > 0) {//如果为空或已取消 s = null; for (Node t = tail; t != null && t != node; t = t.prev) // 从后向前找。 if (t.waitStatus <= 0)//从这里可以看出,<=0的结点,都是还有效的结点。 s = t; } if (s != null) LockSupport.unpark(s.thread);//唤醒 }
这个函数并不复杂。一句话概括:用unpark()唤醒等待队列中最前边的那个未放弃线程,这里我们也用s来表示吧。此时,再和acquireQueued()联系起来,s被唤醒后,进入if (p == head && tryAcquire(arg))的判断(即使p!=head也没关系,它会再进入shouldParkAfterFailedAcquire()寻找一个安全点。这里既然s已经是等待队列中最前边的那个未放弃线程了,那么通过shouldParkAfterFailedAcquire()的调整,s也必然会跑到head的next结点,下一次自旋p==head就成立啦),然后s把自己设置成head标杆结点,表示自己已经获取到资源了,acquire()也返回了。
好了,到这我们就因为把源码看完了,再回头来看下这张图。
是不是就清楚了AQS到底是怎么实现的我们上面的猜想的了吧。那么对应的下课后让你自己去看。
4. 死锁
package com.bobo.thread1;
public class ThreadDemo08 {
/**
* 同步的嵌套
* @param args
*/
public static void main(String[] args) throws InterruptedException {
MyRunable08 runnable = new MyRunable08();
Thread t1 = new Thread(runnable,"A");
Thread t2 = new Thread(runnable,"B");
runnable.flag = true;
t1.start();
Thread.sleep(10);
runnable.flag = false; // 改变状态
t2.start();
}
}
class MyRunable08 implements Runnable{
private Object obj1 = new Object();
private Object obj2 = new Object();
public boolean flag = false;
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " 执行了");
if(flag){
while(true){
synchronized (obj1){ // 加一把锁
System.out.println(Thread.currentThread().getName() + " 执行了1 ** start");
synchronized (obj2){
System.out.println(Thread.currentThread().getName() + " 执行了2 ** start");
System.out.println(Thread.currentThread().getName() + " 执行了2 ** end");
}
System.out.println(Thread.currentThread().getName() + " 执行了1 ** end");
}
}
}else{
while(true){
synchronized (obj2){ // 加一把锁
System.out.println(Thread.currentThread().getName() + " 执行了1 -- start");
synchronized (obj1){
System.out.println(Thread.currentThread().getName() + " 执行了2 -- start");
System.out.println(Thread.currentThread().getName() + " 执行了2 -- end");
}
System.out.println(Thread.currentThread().getName() + " 执行了1 -- end");
}
}
}
}
}
5. ThreadLocal
package com.bobo.thread1;
public class ThreadDemo10 {
/**
* ThreadLocal
* 线程变量
* @param args
*/
public static void main(String[] args) throws Exception{
Runnable ru = new MyRunable10();
Thread t1 = new Thread(ru,"A");
Thread t2 = new Thread(ru,"B");
t1.start();
Thread.sleep(100);
t2.start();
}
}
class MyRunable10 implements Runnable{
// 创建一个ThreadLocal变量
private ThreadLocal<Users> myThreadLocal = new ThreadLocal<>();
@Override
public void run() {
// 将User对象保存在了 当前线程的局部变量中
myThreadLocal.set(new Users("root",((int)(Math.random()*100)) + ""));
System.out.println(Thread.currentThread().getName() + ":" + myThreadLocal.get());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 从当前线程的局部变量中获取保存的对象
System.out.println(Thread.currentThread().getName() + ":" + myThreadLocal.get());
fun1();
}
public void fun1(){
System.out.println(Thread.currentThread().getName() + ":" + myThreadLocal.get());
}
}
class Users{
private String userName;
private String password;
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Users(String userName, String password) {
this.userName = userName;
this.password = password;
}
@Override
public String toString() {
return "User{" +
"userName='" + userName + '\'' +
", password='" + password + '\'' +
'}';
}
}
A:User{userName='root', password='30'}
B:User{userName='root', password='65'}
A:User{userName='root', password='30'}
A:User{userName='root', password='30'}
B:User{userName='root', password='65'}
B:User{userName='root', password='65'}
6.设计模式
6.1 什么是设计模式
6.2 六大设计原则
6.3 单例模式
6.3.1 饿汉式
package com.bobo.singleton;
public class SingetonInstance1 {
/**
* 单例的第一种实现方式
* 饿汉式
* 如果我们提供的有 共有的构造方法,那么外界可以随时的通过 new 构造器 的方式创建很多个实例对象
* 饿汉式的实现步骤:
* 1.私有化构造方法 不让外界直接创建对象
* @param args
*/
public static void main(String[] args) {
User user1 = User.getUser();
System.out.println(user1);
User user2 = User.getUser();
System.out.println(user2);
}
}
class User{
// 2. 声明类型的变量,并实例化,当类被加载的时候就完成了类的实例化,并保存在了内存中
private static final User user = new User();
/**
* 1.私有化构造器 不让外界直接创建对象
*/
private User(){
}
/**
* 3.提供一个对外的静态方法来提供User实例
* @return
*/
public static User getUser(){
return user;
}
}
com.bobo.singleton.User@3b07d329
com.bobo.singleton.User@3b07d329
6.3.2 懒汉式
package com.bobo.singleton;
public class SingetonInstance2 {
/**
* 懒汉式
* @param args
*/
public static void main(String[] args) {
Student stu1 = Student.getStudent();
System.out.println(stu1);
Student stu2 = Student.getStudent();
System.out.println(stu2);
}
}
class Student{
// 声明此类的实例变量,但是没有实例化
private static Student stu = null;
// 私有化构造器 防止外界通过new 直接来实例化
private Student(){
}
// 对外提供一个获取实例的方法
public static Student getStudent(){
if(stu == null){
stu = new Student();
}
return stu;
}
}
package com.bobo.singleton;
public class SingetonInstance3 {
/**
* 懒汉式
* @param args
*/
public static void main(String[] args) {
new Thread(){
@Override
public void run() {
Student1 stu1 = Student1.getStudent();
System.out.println(stu1);
}
}.start();
new Thread(){
@Override
public void run() {
Student1 stu2 = Student1.getStudent();
System.out.println(stu2);
}
}.start();
}
}
class Student1{
// 声明此类的实例变量,但是没有实例化
private static Student1 stu = null;
// 私有化构造器 防止外界通过new 直接来实例化
private Student1(){
}
// 对外提供一个获取实例的方法
public synchronized static Student1 getStudent(){
if(stu == null){
stu = new Student1();
}
return stu;
}
}
com.bobo.singleton.Student1@26f1b9c7
com.bobo.singleton.Student1@26f1b9c7
package com.bobo.singleton;
public class SingetonInstance4 {
/**
* 懒汉式
* @param args
*/
public static void main(String[] args) {
new Thread(){
@Override
public void run() {
Student2 stu1 = Student2.getStudent();
System.out.println(stu1);
}
}.start();
new Thread(){
@Override
public void run() {
Student2 stu2 = Student2.getStudent();
System.out.println(stu2);
}
}.start();
}
}
class Student2{
// 声明此类的实例变量,但是没有实例化
private static Student2 stu = null;
// 私有化构造器 防止外界通过new 直接来实例化
private Student2(){
}
// 对外提供一个获取实例的方法
public static Student2 getStudent(){
if(stu == null){
synchronized (Student2.class){
if(stu == null){
stu = new Student2();
}
return stu;
}
}
return stu;
}
}
com.bobo.singleton.Student2@4809778b
com.bobo.singleton.Student2@4809778b
六、生产者和消费者模型
1.案例代码实现
package com.bobo.thread2;
/**
* 店员
*/
public class Clerk {
// 属性 记录产品的数量
private int product ;
// 进货 从生产者处获取商品
public synchronized void addProduct(){
if(product >= 10){
// 表示商品数量超过了店铺能够放下的容量
System.out.println("货满了,暂停进货...");
// 阻塞操作
try {
wait();// 阻塞
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
// 还有空间,继续进货
product++;
System.out.println(Thread.currentThread().getName() + "生产了一个商品:" + product);
// 唤醒 阻塞的消费者
notify();
}
}
// 卖货 将商品售卖给消费者
public synchronized void sellProduct(){
if(product <=0){
// 表示没有商品了
System.out.println("没有商品了 .....");
try {
// 阻塞 当线程执行再此处的时候,那么就停住
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
// 表示有商品,那就可以售卖了
System.out.println(Thread.currentThread().getName()+"消费了一个商品:" + product--);
// 商品有减少 那么我们可以尝试 解除之前阻塞的线程 继续进货
// 唤醒 阻塞的线程
notify();
}
}
}
package com.bobo.thread2;
import javax.management.relation.RoleUnresolved;
/**
* 生产者
*
*/
public class Producer implements Runnable {
// 关联的店员
private Clerk clerk;
public Producer(Clerk clerk){
this.clerk = clerk;
}
@Override
public void run() {
while(true){
// 生产一个商品给店员
clerk.addProduct();
try {
Thread.sleep((int)(Math.random()*1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
package com.bobo.thread2;
/**
* 消费者
*/
public class Consumer implements Runnable{
// 关联店员
private Clerk clerk;
public Consumer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
while(true){
// 消费者购买了一个商品
clerk.sellProduct();
try {
Thread.sleep((int)(Math.random()*1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
package com.bobo.thread2;
public class ProductTest {
/**
* 生产者(Producer)将产品交给店员(Clerk),而消费者(Consumer)从店员处取走产品,
* 店员一次只能持有固定数量的产品,如果生产者生产了过多的产品,店员叫生产者等一下,
* 如果店中有空位放产品了再通知生产者继续生产;如果店中没有产品了,
* 店员会告诉消费者等一下,如果店中有产品了再通知消费者来取走产品。
*
* 基于面向对象的分析:
* 生产者 Producer
* 功能:
* 生产商品
*
* 消费者 Consumer
* 功能:
* 购买商品
*
* 店员 Clerk
* 属性:
* 商品的数量
* 功能:
* 进货
* 卖货
* @param args
*/
public static void main(String[] args) throws InterruptedException {
// 获取一个店员对象
Clerk clerk = new Clerk();
// 获取生产者对象
Producer producer = new Producer(clerk);
// 获取一个消费者对象
Consumer consumer = new Consumer(clerk);
// 创建两个线程
Thread t1 = new Thread(producer,"生产者:");
Thread t2 = new Thread(consumer,"消费者:");
// 启动线程
t1.start(); // 生产商品
//Thread.sleep(10);
t2.start(); // 消费商品
}
}
2.线程的等待
2.1 sleep方法
2.2 join和yield方法
2.3 wait方法
七、守护线程
1.守护线程的概念
Runnable r1 = new MyRunable();
Thread t1 = new Thread(r1,"A");
// 将一个普通线程设置为一个守护线程
t1.setDaemon(true); // 这个设置必须在start方法执行之前设置
t1.start();
2.TimerTask
class MyTimerTask extends TimerTask {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ":" + new Date());
}
}
package com.bobo.thread4;
import java.util.Calendar;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ExecutorService;
public class ThreadDemo01 {
/**
* Timer
* @param args
*/
public static void main(String[] args) throws Exception {
// 获取一个Timer对象
Timer timer = new Timer("A");
// 获取任务对象
MyTimerTask myTimerTask = new MyTimerTask();
// 任务调度 指定的任务 开始的时间 下次执行的间隔时间
//timer.schedule(myTimerTask,1000,1000);
timer.schedule(myTimerTask,getDate(),1000);
Thread.sleep(5000);
System.out.println("main 结束了");
}
/**
* 获取定时任务执行的时间
* @return
*/
public static Date getDate(){
Calendar c = Calendar.getInstance();
c.set(Calendar.HOUR_OF_DAY,14);
c.set(Calendar.MINUTE,44);
c.set(Calendar.SECOND,0);
return c.getTime();
}
}
/**
* 定时器对应的要执行的任务
*/
class MyTimerTask extends TimerTask {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ":" + new Date());
}
}
八、线程池
1.线程池的相关概念
2.线程池的具体使用
2.1 newCachedThreadPool
package com.bobo.threadpool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolDemo01 {
/**
* 线程池的实现
* @param args
*/
public static void main(String[] args) {
// 创建一个可缓存的线程池
ExecutorService executorService = Executors.newCachedThreadPool();
MyRunable myRunable = new MyRunable();
// 从线程池中获取一个线程 执行 Runable任务
executorService.execute(myRunable);
executorService.execute(myRunable);
executorService.execute(myRunable);
}
}
class MyRunable implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ":执行了...");
}
}
pool-1-thread-1:执行了...
pool-1-thread-3:执行了...
pool-1-thread-2:执行了...
2.2 newFixedThreadPool
package com.bobo.threadpool;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolDemo02 {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(5);
for (int i = 0 ; i < 10; i++){
executorService.execute(new MyRunable02());
}
}
}
class MyRunable02 implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " 执行了...");
}
}
pool-1-thread-1 执行了...
pool-1-thread-1 执行了...
pool-1-thread-1 执行了...
pool-1-thread-1 执行了...
pool-1-thread-1 执行了...
pool-1-thread-1 执行了...
pool-1-thread-2 执行了...
pool-1-thread-3 执行了...
pool-1-thread-5 执行了...
pool-1-thread-4 执行了...
2.3 newScheduledThreadPool
package com.bobo.threadpool;
import java.util.concurrent.*;
public class ThreadPoolDemo03 {
public static void main(String[] args) {
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(5);
for (int i = 0 ; i < 10 ; i ++){
executorService.schedule(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ":延长一秒执行");
}
}, 1, TimeUnit.SECONDS);
}
}
}
pool-1-thread-4:延长一秒执行
pool-1-thread-5:延长一秒执行
pool-1-thread-1:延长一秒执行
pool-1-thread-2:延长一秒执行
pool-1-thread-3:延长一秒执行
pool-1-thread-4:延长一秒执行
pool-1-thread-5:延长一秒执行
pool-1-thread-2:延长一秒执行
pool-1-thread-1:延长一秒执行
pool-1-thread-3:延长一秒执行
2.4 newSingleThreadExecutor
package com.bobo.threadpool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolDemo04 {
public static void main(String[] args) {
ExecutorService executorService = Executors.newSingleThreadExecutor();
for(int i =0 ;i < 5 ; i++){
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+":执行了...");
}
});
}
}
}
pool-1-thread-1:执行了...
pool-1-thread-1:执行了...
pool-1-thread-1:执行了...
pool-1-thread-1:执行了...
pool-1-thread-1:执行了...