传送门==>B站遇见狂神说Java多线程详解
做笔记时有的知识点并没有整理;
ml
1.线程创建之继承Thread类
- 首先自定义线程类继承Thread类;
- 然后是重写run( )方法;编写线程执行体
- 然后创建自定义线程对象;使用start()方法启动线程
注意:
线程开启并不一定是直接就执行;得看CPU的调度,或者说的根据CPU的实际资源情况
;
代码练习:
创建MyThread
/**
* 线程创建之继承Thread类;
*/
//1.首先继承Thread类;
public class MyThread01 extends Thread {
//2.然后是重写run方法;编写执行的线程;
@Override
public void run() {
//run方法 线程的执行体;
for (int i = 0; i < 100; i++) {
System.out.println("我是run方法->"+i);
}
}
//mian方法;(主线程)
public static void main(String[] args) {
//创建线程对象;
MyThread01 myThread01=new MyThread01();
//3.使用start方法启动线程;
myThread01.start();
//main方法 线程的执行体;
for (int i = 0; i < 100; i++) {
System.out.println("我是main方法->"+i);
}
}
}
线程交替执行
而如果不使用start方法启动线程呢;直接在main方法中调用run方法;那就按着方法的调用顺序执行了,依次有序地执行
图片下载练习
//继承Thread类;
public class MyThread02 extends Thread{
//定义网页地址和文件名;
private String url;
private String name;
//构造方法;初始化;
public MyThread02(String url, String name) {
this.url = url;
this.name = name;
}
//重写run方法
@Override
public void run() {
//创建下载器对象;
WebDownLoader wdl=new WebDownLoader();
//调用下载方法;
wdl.dowLoade(url,name);
System.out.println("下载文件名=>"+name);
}
//主方法;
public static void main(String[] args) {
MyThread02 myT1=new MyThread02("https://www.baidu.com/link?url=dvRgPF9UDWw42jQF0lr8EnKMw2XfU_NKhWqanB4wL85sqwWrGBVnBAGd3zBJUJ0JZa0d3ryBEd6FCiZfrG1ax0o3EkygROiBPwUrzEFRoC4DK6S5DSiMz1Q1Ge_O8vyNbE8IzW1Kn2WGbHDgGzx4yhlQsOadV1rFqemy4Os4lhBMAxGLXZ2CY5ftLFMYzxUgZbFIw5qtkPUajas-nPD4yyk118flZrp6Dj6HOAfwKB-X0xbxH2ttXMocCGzVHuwGm0a0Orw8A_cqyOm3tDvXMhKu3YSYHtKDkisZqupVBI3pJhhvP97RCXacC0jtynAXvUrPz4OAhT_QvMfWp7akjFP5gBFiK_5Tt2BwRchJ1YgenbZwqGucGAopjBtmlKeAKOmkaVJ12wiG6u7Y85YNIlGo__O0ZgurYtRb4WbOZTxsBX_e0XrBB0klH2V2BJM3YuEyMpc_6sZdwhYnj4JCltSh1oca5jDlQ51Tu2LHqI-SeDhvi_ioBAno4NzkQqEI_rhRIJSUqb7EVBjO7sTEB8qohgcbM9ZtD-3fqTYTRZBQ97W0o5p2tUTEVuXDTM0FeunmEEoiaLeBCSJbB8to09OYCCy9C-e2CZyaaiIAnXvAlAILYMaHh0a1ANXLzFR1&wd=&eqid=9a0261300016198200000005611d721a","图1.jpg");
MyThread02 myT2=new MyThread02("https://www.baidu.com/link?url=dvRgPF9UDWw42jQF0lr8EnKMw2XfU_NKhWqanB4wL85sqwWrGBVnBAGd3zBJUJ0JZa0d3ryBEd6FCiZfrG1ax0o3EkygROiBPwUrzEFRoC4DK6S5DSiMz1Q1Ge_O8vyNbE8IzW1Kn2WGbHDgGzx4yhlQsOadV1rFqemy4Os4lhBMAxGLXZ2CY5ftLFMYzxUgZbFIw5qtkPUajas-nPD4yyk118flZrp6Dj6HOAfwKB-X0xbxH2ttXMocCGzVHuwGm0a0Orw8A_cqyOm3tDvXMhKu3YSYHtKDkisZqupVBI3pJhhvP97RCXacC0jtynAXvUrPz4OAhT_QvMfWp7akjFP5gBFiK_5Tt2BwRchJ1YgenbZwqGucGAopjBtmlKeAKOmkaVJ12wiG6u7Y85YNIlGo__O0ZgurYtRb4WbOZTxsBX_e0XrBB0klH2V2BJM3YuEyMpc_6sZdwhYnj4JCltSh1oca5jDlQ51Tu2LHqI-SeDhvi_ioBAno4NzkQqEI_rhRIJSUqb7EVBjO7sTEB8qohgcbM9ZtD-3fqTYTRZBQ97W0o5p2tUTEVuXDTM0FeunmEEoiaLeBCSJbB8to09OYCCy9C-e2CZyaaiIAnXvAlAILYMaHh0a1ANXLzFR1&wd=&eqid=9a0261300016198200000005611d721a","图2.jpg");
MyThread02 myT3=new MyThread02("https://www.baidu.com/link?url=dvRgPF9UDWw42jQF0lr8EnKMw2XfU_NKhWqanB4wL85sqwWrGBVnBAGd3zBJUJ0JZa0d3ryBEd6FCiZfrG1ax0o3EkygROiBPwUrzEFRoC4DK6S5DSiMz1Q1Ge_O8vyNbE8IzW1Kn2WGbHDgGzx4yhlQsOadV1rFqemy4Os4lhBMAxGLXZ2CY5ftLFMYzxUgZbFIw5qtkPUajas-nPD4yyk118flZrp6Dj6HOAfwKB-X0xbxH2ttXMocCGzVHuwGm0a0Orw8A_cqyOm3tDvXMhKu3YSYHtKDkisZqupVBI3pJhhvP97RCXacC0jtynAXvUrPz4OAhT_QvMfWp7akjFP5gBFiK_5Tt2BwRchJ1YgenbZwqGucGAopjBtmlKeAKOmkaVJ12wiG6u7Y85YNIlGo__O0ZgurYtRb4WbOZTxsBX_e0XrBB0klH2V2BJM3YuEyMpc_6sZdwhYnj4JCltSh1oca5jDlQ51Tu2LHqI-SeDhvi_ioBAno4NzkQqEI_rhRIJSUqb7EVBjO7sTEB8qohgcbM9ZtD-3fqTYTRZBQ97W0o5p2tUTEVuXDTM0FeunmEEoiaLeBCSJbB8to09OYCCy9C-e2CZyaaiIAnXvAlAILYMaHh0a1ANXLzFR1&wd=&eqid=9a0261300016198200000005611d721a","图3.jpg");
//启动线程;
myT1.start();
myT2.start();
myT3.start();
}
}
//创建下载器;
class WebDownLoader{
//下载方法;
public void dowLoade(String url,String name){
try {
FileUtils.copyURLToFile(new URL(url),new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("下载方法有问题, IO异常");
}
}
}
运行;下载时并不会按照顺序,
2.线程创建之实现Runnable接口
可避免单继承的局限性;方便一个对象被多个线程使用.
- 自定义类实现Runnable接口;
- 重写run()方法,编写线程执行体
- 创建自定义线程对象放入Thread对象中,start()方法启动线程
代码练习
/**
* 创建线程之实现Runnable接口
*/
//实现Runnable接口;
public class MyThread03 implements Runnable {
//重写run方法;
@Override
public void run() {
//run方法 线程的执行体;
for (int i = 0; i < 100; i++) {
System.out.println("我是run方法->" + i);
}
}
//主方法;
public static void main(String[] args) {
//创建实现类的对象;
MyThread03 myThread03 = new MyThread03();
//创建线程对象;启动线程
new Thread(myThread03).start();
//main方法 线程的执行体;
for (int i = 0; i < 100; i++) {
System.out.println("我是main方法->" + i);
}
}
}
买票案例
多个线程去操作同一份资源,线程不安全
//实现Runnable接口;
public class MyThreadToTicket implements Runnable {
//定义车票数量;
private int ticket=10;
//重写run方法;
@Override
public void run() {
while(true) {
//票买完就停了;
if(ticket<=0) {
break;
}
try {
//定义延时;
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
//Thread.currentThread().getName():可获取到当前线程的名字;
System.out.println(Thread.currentThread().getName() + "::买到了第" + ticket-- + "个票");
}
}
//主方法;
public static void main(String[] args) {
//创建自定义类的对象;
MyThreadToTicket mt=new MyThreadToTicket();
//创建Thread对象,启动线程;
new Thread(mt,"阿杰").start();
new Thread(mt,"阿猫").start();
new Thread(mt,"阿伟").start();
}
}
运行时,出现两个人买到同一张票的问题.
模拟龟兔赛跑
//实现Runnable接口;
public class TorAndRabRun implements Runnable {
//定义胜利者;
private static String win;
//判断是否结束比赛; step:步数;
private boolean over(int step){
//若已有获胜者,则结束;
if(win!=null){
return true;
}{
//若已经跑完了,结束比赛;
if(step>=100){
win=Thread.currentThread().getName();
System.out.println("获胜者" + win);
return true;
}
}
return false;
}
//重写run方法
@Override
public void run() {
for (int i = 0; i <= 101; i++) {
//模拟兔子睡觉;
if(Thread.currentThread().getName().equals("兔纸")&&i%10==0){
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//若比赛结束,终止循环;
if (over(i)) {
break;
}
//Thread.currentThread().getName():会获取线程名;
System.out.println(Thread.currentThread().getName()+"==>跑"+i+"米");
}
}
//主方法;
public static void main(String[] args) {
//创建赛道对象;
TorAndRabRun tar=new TorAndRabRun();
//创建线程对象,启动线程;
new Thread(tar,"乌龟").start();
new Thread(tar,"兔纸").start();
}
}
3.线程创建之实现Callable接口
- 实现callable接口,需要返回值类型;
- 重写call方法;需要抛出异常类型;
- 创建自定义线程的对象;
- 开启服务;
- 提交执行;
- 获取结果;
- 关闭服务
使用之前的多线程下载图片案例;
/**
* 线程创建之实现Callable接口;和前两种不同的是该方式可产生返回值;
*/
//实现Callable接口;
public class MyThread04 implements Callable<Boolean> {
//定义网页地址和文件名;
private String url;
private String name;
//构造方法;初始化;
public MyThread04(String url, String name) {
this.url = url;
this.name = name;
}
//重写callable方法;
@Override
public Boolean call() throws Exception {
//创建下载器对象;
WebDownLoader wdl=new WebDownLoader();
//调用下载方法;
wdl.dowLoade(url,name);
System.out.println("下载文件名=>"+name);
return null;
}
//主方法
public static void main(String[] args) throws ExecutionException, InterruptedException{
//创建实现类的对象;
MyThread04 myT1=new MyThread04("https://img-blog.csdnimg.cn/20210613214620230.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L01yVHVtbnVz,size_16,color_FFFFFF,t_70","图片1.png");
MyThread04 myT2=new MyThread04("https://img-blog.csdnimg.cn/20210613214620230.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L01yVHVtbnVz,size_16,color_FFFFFF,t_70","图片2.png");
MyThread04 myT3=new MyThread04("https://img-blog.csdnimg.cn/20210613214620230.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L01yVHVtbnVz,size_16,color_FFFFFF,t_70","图片3.png");
//开启服务;(创建线程池)
ExecutorService es= Executors.newFixedThreadPool(3);
//执行服务提交;
Future<Boolean> ft1=es.submit(myT1);
Future<Boolean> ft2=es.submit(myT2);
Future<Boolean> ft3=es.submit(myT3);
//获取返回结果;
Boolean res1 = ft1.get();
Boolean res2 = ft2.get();
Boolean res3 = ft3.get();
System.out.print(res1);
System.out.print(res2);
System.out.print(res3);
//关闭服务;
es.shutdownNow();
}
}
//创建下载器;
class WebDownLoader{
//下载方法;
public void dowLoade(String url,String name){
try {
FileUtils.copyURLToFile(new URL(url),new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("下载方法有问题, IO异常");
}
}
}
也是交替执行的;
4.静态代理模式
- 真实对象(目标对象)和代理对象都要实现同一个接口;
- 代理对象要代理真实角色;
- 代理对象可实现真实对象不能完成的方法;
- 真实对象专注自己的实现;
public class StaticProxy {
public static void main(String[] args) {
//代理 | 真实 | 需要完成的事情;
//new Thread(()->System.out.println("完成")).start();
/* //创建真实角色对象;
Person ps=new Person();
//创建代理角色对象;
ProxyPerson prop=new ProxyPerson(ps);
prop.Happy();*/
//实际上,可精简为;
// 代理 | 真实 | 需要完成的事情;
new ProxyPerson(new Person()).Happy();
}
}
//接口;
interface Marry{
//抽象方法;
void Happy();
}
//人;(真实角色)
class Person implements Marry{
@Override
public void Happy() {
System.out.println("真实角色出场");
}
}
//中介;(代理角色)
class ProxyPerson implements Marry{
//定义目标对象1;
private Marry targetP;
//构造方法;
public ProxyPerson(Marry targetP) {
this.targetP = targetP;
}
@Override
public void Happy() {
//调用之前要完成的方法;
brfore();
//执行方法;
this.targetP.Happy();
//调用之后要完成的方法
after();
}
//之前要进行的方法;
private void brfore() {
System.out.println("前面要完成的事情;");
}
//之后要进行的方法;
private void after() {
System.out.println("后面要完成的事情;");
}
}
5.Lambda表达式
lambda:
λ:希腊字母表第11位;
使用目的是为了避免匿名内部类定义过多;实质上属于函数式编程;
-----------------------------------
(params)->expression [表达式]
(params)->statement (语句)
-----------------------------------
函数式接口:(Functional Interface)
----------------------------------
函数式接口定义:
只包含唯一的抽象方法;
可使用lambda表达式创建接口对象
练习;使用不同的方式实现输出接口方法;
public class DemoLamDa01 {
//2.使用静态内部类;
static class FunImpl02 implements FunMeth{
@Override
public void OnlyMethod() {
System.out.println("FunImpl02静态内部类 实现了 FunMeth");
}
}
public static void main(String[] args) {
//3.使用局部内部类
class FunImpl03 implements FunMeth{
@Override
public void OnlyMethod() {
System.out.println("FunImpl03局部内部类 实现了 FunMeth");
}
}
//(1)创建实现类对象;调用方法;
FunMeth fm1=new FunImpl01();
fm1.OnlyMethod();
//(2)创建静态内部类对象;调用方法;
fm1=new FunImpl02();
fm1.OnlyMethod();
//(3)创建局部内部类对象;调用方法
fm1=new FunImpl03();
fm1.OnlyMethod();
//4.使用匿名内部类;
fm1=new FunMeth() {
@Override
public void OnlyMethod() {
System.out.println("FunImpl04匿名内部类 实现了 FunMeth");
}
};
//(4)使用匿名内部类调用方法
fm1.OnlyMethod();
//(5)直接使用lamda表达式的方式
fm1=() -> System.out.println("Lambda表达式 -->FunMeth");
fm1.OnlyMethod();
}
}
//定义函数式接口;(即只有一个抽象方法)
interface FunMeth{
void OnlyMethod();
}
//1.接口的实现类;
class FunImpl01 implements FunMeth{
@Override
public void OnlyMethod() {
System.out.println("FunImpl01 实现了 FunMeth");
}
}
FunImpl01 实现了 FunMeth
FunImpl02静态内部类 实现了 FunMeth
FunImpl03局部内部类 实现了 FunMeth
FunImpl04匿名内部类 实现了 FunMeth
Lambda表达式 -->FunMeth
- 前提是接口为函数式接口;(仅有一个抽象方法);
- 若想要简化为一行lambda表达式,那么就只能输出一行代码;若需输出多行,lambda要加代码块包裹;
简化lambda练习 - 多个参数类型也可以简化,但是要用括号( )把参数包裹起来;
public class DemoLamDa02 {
public static void main(String[] args) {
int a1=11;
int a2=12;
int a3=13;
int a4=14;
//1.使用lambda表达式;
FunMeth fm=(int a) -> { System.out.println("实现方法-且a1="+a1);};
fm.OnlyMethod(a1);
//2.简化;--去掉参数类型;
FunMeth fm2=(a) -> { System.out.println("实现方法-且a2="+a2);};
fm2.OnlyMethod(a2);
//3.简化---去掉参数的括号();
FunMeth fm3=a -> { System.out.println("实现方法-且a3="+a3);};
fm3.OnlyMethod(a3);
//4.简化---去掉大括号{};
FunMeth fm4=(int a) -> System.out.println("实现方法-且a4="+a4);
fm4.OnlyMethod(a4);
}
}
//函数式接口;
interface FunMeth{
void OnlyMethod(int a);
}
实现方法-且a1=11
实现方法-且a2=12
实现方法-且a3=13
实现方法-且a4=14
6.线程状态:
(1)线程创建(新生成线程):
new Thread( ) ;创建后就进入新生状态;
(2)调用start方法,线程就进入就绪状态;
(3)就绪状态的线程,被调度后进入运行态;开始执行线程体中的代码;
(4)若调用了sleep( )睡眠,/wait( )等待,/同步锁定;线程进入阻塞态;只能等阻塞结束后,进入就绪态.
(5)线程中断/结束;进入死亡.
常用方法 | 说明 |
---|---|
setPriority( int state) | 修改线程优先级 |
static void sleep (long millis) | 指定毫秒时间内线程休眠 |
void join() | 等待线程终止 |
static void yield() | 暂停当前线程对象 执行其他线程 |
void interrupt() | 中断线程 |
boolean isAlive() | 测试线程是否活着 |
线程停止
- 使用一个标记变量终止线程;
- 线程正常停止;(尽量不要死循环);
- 尽量不使用stop方法;destory方法等过时方法;
//实现Runnable接口;
public class Mythread05 implements Runnable{
//1.设置标志;
private boolean flag=true;
//重写run方法;
@Override
public void run() {
int i=0;
while (flag){
System.out.println("自定义线程执行中...."+i++);
}
}
//2.自定义线程停止方法;
public void stop(){
this.flag=false;
}
//主方法;
public static void main(String[] args) {
//创建实现类对象;
Mythread05 myt5=new Mythread05();
//创建线程对象,启动线程;
new Thread(myt5).start();
for (int i = 0; i < 500; i++) {
System.out.println("主线程执行...."+i);
if(i==200){
myt5.stop();
System.out.println("自定义线程停止");
}
}
}
}
达到对应数字,自定义线程停止;
线程休眠 --sleep方法
使用sleep方法;
sleep(long time)
指定线程休眠的时间(毫秒)- sleep设置的时间结束后,线程就跑到就绪态
- sleep存在异常
InterruptedException
; - 可用来模拟倒计时;每个对象带有锁,但是sleep不会释放锁
模拟倒计时
//实现Runnable接口;
public class SleepTime {
//自定义倒计时方法;
public static void timeOver() throws InterruptedException {
int time=10;
//设置线程休眠时间1秒;
while (true) {
Thread.sleep(1000);
System.out.println("倒计时=>"+time--);
//到达指定数字,结束循环;
if(time<=0){
break;
}
}
}
//主方法;
public static void main(String[] args) {
//调用方法;
try {
timeOver();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
更新打印输出当前系统时间
//实现Runnable接口;
public class SleepNow {
public static void main(String[] args) {
//获取当前系统时间;
Date startTime = new Date(System.currentTimeMillis());
//进行循环;
while (true){
try {
//使得线程休眠1秒;
Thread.sleep(1000);
//将当前时间格式化;
System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime));
//1秒更新一次时间;
startTime=new Date(System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
线程礼让—yield方法
- 使得当前正在执行得线程暂停,但是不阻塞;
- 线程礼让不一定会成功;是要让CPU重新调度;(
可能就是这个线程刚进入就绪态,就马上获得了CPU资源,进入运行态,不给别的线程机会;所以说线程礼让不一定成功
) - 线程由运行态到就绪态
线程礼让练习
//实现Runnable接口;
public class MyThread07 implements Runnable {
//重写run方法;
@Override
public void run() {
//Thread.currentThread().getName():获取当前线程名;
System.out.println(Thread.currentThread().getName()+"线程开始运行");
//调用线程礼让方法;
Thread.yield();
System.out.println(Thread.currentThread().getName()+"线程停止");
}
//主方法;
public static void main(String[] args) {
//创建实现类的对象;
MyThread07 a = new MyThread07();
MyThread07 b = new MyThread07();
MyThread07 c = new MyThread07();
//创建线程对象;启动线程
new Thread(a,"壹号").start();
new Thread(b,"贰号").start();
new Thread(c,"叁号").start();
}
}
线程礼让不一定成功;
线程强制执行 — join方法
join方法;让当前线程先执行结束,其他线程阻塞,再去执行其他线程;
练习
//实现Runnable接口;
public class MyThread08 implements Runnable {
//重写run方法;
@Override
public void run() {
//Thread.currentThread().getName():获取当前线程名;
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName()+"线程运行==>"+i);
}
}
//主方法;
public static void main(String[] args) {
//创建实现类的对象;
MyThread08 a = new MyThread08();
//创建线程对象;启动线程
Thread t1=new Thread(a,"壹号");
t1.start();
for (int i = 0; i < 500; i++) {
System.out.println("主线程执行=>"+i);
if(i==200){
//当数字到200时,线程1号强制执行;
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
线程壹号强制执行
观测线程状态
在jdk说明文档Api中搜索线程状态–Thread.State
线程状态。 线程可以处于以下状态之一
(一个线程可以在给定时间点处于一个状态。 这些状态是不反映任何操作系统线程状态的虚拟机状态。 ):
- NEW
尚未启动的线程处于此状态。- RUNNABLE
在Java虚拟机中执行的线程处于此状态。- BLOCKED
被阻塞等待监视器锁定的线程处于此状态。- WAITING
正在等待另一个线程执行特定动作的线程处于此状态。- TIMED_WAITING
正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。- TERMINATED
已退出的线程处于此状态。
练习线程状态
public class MyThread09 {
public static void main(String[] args) {
Thread thread = new Thread(() ->{
for (int i = 0; i < 2; i++) {
try {
//线程休眠;
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("执行语句");
});
//获取线程状态且输出;
Thread.State state = thread.getState();
System.out.println(state);
//启动线程;
thread.start();
state=thread.getState();
//查看线程状态;
System.out.println(state);
//只要线程不停止就循环;
while (state!= Thread.State.TERMINATED){
try {
//设置休眠;
Thread.sleep(100);
//更新线程状态且输出;
state=thread.getState();
System.out.println(state);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
运行可观测到线程的几个状态
NEW
RUNNABLE
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
TIMED_WAITING
执行语句
TERMINATED
线程优先级
线程优先级的范围由1到10
其中; MIN_PRIORITY表示1,NORM_PRIORITY表示5;MAX_PRIORITY表示10
可使用方法获取线程优先级:getPriority;
设置优先级:setPriority(int priority)
java使用线程调度器监控程序中启动后进入就绪态的所有线程;线程调度器按照优先级决定应调度哪个线程执行
- 注意:设置完优先级再启动线程;
- 优先级低不一定就执行晚
练习
public class MyThread10 implements Runnable {
//重写run方法;
@Override
public void run() {
//Thread.currentThread().getName():获取当前线程名;
//Thread.currentThread().getPriority():获取当前线程优先级;
System.out.println(Thread.currentThread().getName()+"优先级为=>"+Thread.currentThread().getPriority());
}
public static void main(String[] args) {
//主线程优先级为5
System.out.println(Thread.currentThread().getName()+"优先级为=>"+Thread.currentThread().getPriority());
//创建实现类对象
MyThread10 myth=new MyThread10();
//创建线程对象;
Thread t1=new Thread(myth,"甲");
Thread t2=new Thread(myth,"乙");
Thread t3=new Thread(myth,"丙");
Thread t4=new Thread(myth,"丁");
Thread t5=new Thread(myth,"戊");
//设置线程优先级;然后再启动线程;
t1.setPriority(2);
t1.start();
t2.setPriority(10);
t2.start();
t3.setPriority(6);
t3.start();
t4.setPriority(5);
t4.start();
t5.setPriority(3);
t5.start();
}
}
优先级低的不一定就执行晚
守护线程
- 线程分为用户线程与守护线程(daemon)
- 虚拟机必须确保用户线程执行完毕;但是不需要等待守护线程执行完毕;
- 常见的守护线程:监控内存,垃圾回收,后台记录日志
- 使用方法setDaemon设置守护线程,该方法默认为false
练习
//守护线程学习;
public class GuardThread {
public static void main(String[] args) {
//创建上帝线程;
God god=new God();
Thread t1=new Thread(god);
//设置上帝为守护线程;
t1.setDaemon(true);
//启动守护线程;
t1.start();
//创建人类线程;
Person ps=new Person();
//启动用户线程
new Thread(ps).start();
}
}
//模拟用户线程---人类;
class Person implements Runnable{
//重写run方法;
@Override
public void run() {
for (int i = 0; i < 36500; i++) {
System.out.println("人类生活");
}
System.out.println("---人类生活结束----");
}
}
//模拟守护线程---上帝;
class God implements Runnable{
//重写run方法;
@Override
public void run() {
while (true){
System.out.println("<=上帝守护=>");
}
}
}
用户线程结束,但守护线程并未结束
7.线程同步机制
并发 :多个线程操作同一个对象
线程同步:
处理多线程问题时,多个线程访问同一个对象,并且某些线程还要修改这个对象;这时就需要线程同步,线程同步作为等待机制;多个需要同时访问当前对象的线程进入这个对象的等待池形成队列,等待前面的线程使用完毕,下一个线程才会再次使用…
由于同一个进程的多个线程共享同一块存储空间,可能有访问冲突问题,为保证数据在方法中被访问时的正确性,在访问时加入锁机制(使用关键字synchronized),当一个线程获得对象的排他锁,独占了资源,其他线程必须等待,使用后释放锁才行.
- 一个线程持有锁时,会导致其他所有需要这个锁的线程挂起;
- 在多线程竞争情况下,加锁释放锁会导致一些上下文切换和调度延迟,引起性能问题;
- 若一个高优先级的线程在等待一个低优先级的线程释放锁,会导致优先级倒置,引起性能问题
三个不安全线程案例
(1)不安全的买票案例:
可能出现多个人买到同一张票,或者负数票的结果.
public class NotSafeDemo01 {
public static void main(String[] args) {
//创建买票类对象;
BuyTicket by=new BuyTicket();
//创建线程且启动;
new Thread(by,"阿智").start();
new Thread(by,"阿猫").start();
new Thread(by,"阿狗").start();
}
}
//买票类;
class BuyTicket implements Runnable{
//定义票的数量;
private int ticket=10;
//定义的标志位;
boolean flag=true;
//重写的run方法;
@Override
public void run() {
//买票;
while (flag){
try {
toBuy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//买票方法;
private void toBuy() throws InterruptedException {
//判断是否还有票;
if(ticket<=0){
flag=false;
return;
}
//设置线程休眠;
Thread.sleep(100);
//输出买票信息;
System.out.println(Thread.currentThread().getName()+"买到了票=>"+ticket--);
}
}
(2)不安全的银行取钱案例
出现取款错误问题
public class NotSafeDemo02 {
public static void main(String[] args) {
//设置账户;
Account account=new Account(200,"存款");
//创建取钱线程;
ByBank bb1=new ByBank(account,150,"阿猫");
ByBank bb2=new ByBank(account,150,"阿狗");
//启动线程;
bb1.start();
bb2.start();
}
}
//账户类;
class Account {
//定义存款和存款卡名;
int money;
String name;
public Account(int money, String name) {
this.money = money;
this.name = name;
}
}
//银行类;
class ByBank extends Thread{
//定义账户;
Account account;
//定义取款;
int getMoney;
//定义手中的钱数;
int nowMoney;
//构造方法;
public ByBank(Account account, int getMoney, String name) {
super(name);
this.account = account;
this.getMoney = getMoney;
}
//取钱;
@Override
public void run() {
//判断是否还有钱;
if(account.money-getMoney<0){
System.out.println(Thread.currentThread().getName()+"钱不够用,取不了");
return;
}
try {
//设置线程休眠;
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//卡余额:存款-取款;
account.money=account.money-getMoney;
//现在手里的钱;
nowMoney=nowMoney+getMoney;
System.out.println(account.name+"余额==>"+account.money);
//this.getName()也就是当前线程名 Thread.currentThread().getName()
System.out.println(this.getName()+"手里的钱=>"+nowMoney);
}
}
(3)线程不安全的集合
public class NotSafeDemo03 {
public static void main(String[] args) {
//新建集合;
ArrayList<String> arrayList=new ArrayList<>();
for (int i = 0; i < 10000; i++) {
//创建线程->启动线程;
new Thread(()->{
//向集合添加元素;==>线程名
arrayList.add(Thread.currentThread().getName());
}).start();
}
try {
//设置线程休眠;
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("集合元素数量=>"+arrayList.size());
}
}
由于线程不安全,可能会出现元素添加实际个数错误;
同步方法以及同步块
同步方法
由于可使用private
关键字保证数据对象只能被方法访问,那么就需要针对方法使用synchronized关键字-同步机制
;包括同步方法和同步块
隐式的锁
同步方法案例:
public synchronized void method( .参数/无参…){ .方法体…}
同步方法控制对对象
的范围,而每个对象对应一把锁 , 每个同步方法都必须获得当前调用方法的对象的锁才能执行;否则线程会被阻塞;方法一旦执行时,就会独占这把锁,直到该方法返回才能释放锁,后面被阻塞的线程获得这个锁后才能执行;
但是同步方法有 缺点–>若将一个较复杂的方法设置为同步方法会影响效率;
若使用了过多的锁,可能会浪费资源
同步块
案例: synchronized(对象){…代码体.}
对象:同步监视器
这里括号中放入的对象可以是任何对象;但是一般为共享资源(或者说是用来增删改查的对象);
同步方法中无需指定同步监视器,由于同步方法中的同步监视器就是this,即这个对象本身,或者class
;
同步监视器的执行过程;
- 1号线程访问,锁定同步监视器,执行代码;
- 2号线程访问,但是同步监视器此时锁定,无法访问;
- 1号线程访问完毕,解锁同步监视器;
- 2号线程访问,锁定同步监视器,执行代码.
修改刚才的不安全线程买票案例
在买票方法处加上synchronized关键字,变为同步方法;
public class NotSafeDemo01 {
public static void main(String[] args) {
//创建买票类对象;
BuyTicket by=new BuyTicket();
//创建线程且启动;
new Thread(by,"阿智").start();
new Thread(by,"阿猫").start();
new Thread(by,"阿狗").start();
}
}
//买票类;
class BuyTicket implements Runnable{
//定义票的数量;
private int ticket=10;
//定义的标志位;
boolean flag=true;
//重写的run方法;
@Override
public void run() {
//买票;
while (flag){
try {
toBuy();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//买票方法;
//使用synchronized关键字,变为同步方法;锁的是this;
private synchronized void toBuy() throws InterruptedException {
//判断是否还有票;
if(ticket<=0){
flag=false;
return;
}
//设置线程休眠;
Thread.sleep(100);
//输出买票信息;
System.out.println(Thread.currentThread().getName()+"买到了票=>"+ticket--);
}
}
买票过程井然有序
修改刚才的不安全线程取款案例
注意要把共享资源账户作为同步监视器,用同步代码块将所有的取款操作锁起来;
public class NotSafeDemo02 {
public static void main(String[] args) {
//设置账户;
Account account = new Account(200, "存款");
//创建取钱线程;
ByBank bb1 = new ByBank(account, 150, "阿猫");
ByBank bb2 = new ByBank(account, 150, "阿狗");
//启动线程;
bb1.start();
bb2.start();
}
}
//账户类;
class Account {
//定义存款和存款卡名;
int money;
String name;
public Account(int money, String name) {
this.money = money;
this.name = name;
}
}
//银行类;
class ByBank extends Thread {
//定义账户;
Account account;
//定义取款;
int getMoney;
//定义手中的钱数;
int nowMoney;
//构造方法;
public ByBank(Account account, int getMoney, String name) {
super(name);
this.account = account;
this.getMoney = getMoney;
}
//取钱;
@Override
public void run() {
//注意将共享资源账户:account;作为同步监视器;
synchronized (account) {
//判断是否还有钱;
if (account.money - getMoney < 0) {
System.out.println(Thread.currentThread().getName() + "钱不够用,取不了");
return;
}
try {
//设置线程休眠;
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//卡余额:存款-取款;
account.money = account.money - getMoney;
//现在手里的钱;
nowMoney = nowMoney + getMoney;
System.out.println(account.name + "余额==>" + account.money);
//this.getName()也就是当前线程名 Thread.currentThread().getName()
System.out.println(this.getName() + "手里的钱=>" + nowMoney);
}
}
}
取钱过程井然有序
修改刚才的不安全线程的集合案例
将集合对象作为同步监视器,用同步代码块将添加方法包裹起来;
public class NotSafeDemo03 {
public static void main(String[] args) {
//新建集合;
ArrayList<String> arrayList=new ArrayList<>();
for (int i = 0; i < 10000; i++) {
//创建线程->启动线程;
new Thread(()->{
//向集合添加元素;==>线程名
//将集合对象作为同步监视器;
synchronized (arrayList){
arrayList.add(Thread.currentThread().getName());
}
}).start();
}
try {
//设置线程休眠;
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("集合元素数量=>"+arrayList.size());
}
}
所有元素已正常添加
CopyOnWriteArrayList(线程安全的集合之一)
public class Demo {
public static void main(String[] args) {
//创建集合;
CopyOnWriteArrayList cwa=new CopyOnWriteArrayList();
for (int i = 0; i < 10000; i++) {
new Thread(()->{
cwa.add(Thread.currentThread().getName());
}).start();
}
try {
//线程休眠设置;
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("集合元素个数->"+cwa.size());
}
}
不使用同步关键字,本身就是线程安全的集合;
8.死锁
多个线程各自占有一些共享资源,并且相互等待其他线程占有的资源才能运行,而导致的两个或多个线程都在等待对方释放资源,都停止执行了;
一个同步块同时拥有 两个以上对象的锁,可能发生死锁
产生死锁的四个条件:
- 互斥:一个资源每次只能被一个进程使用;
- 请求与保持条件:一个进程由于请求资源导致阻塞,对于已经获取的资源保持不放;
- 不剥夺条件;进程已获得的资源,在未使用完之前,不能强行剥夺;
- 循环等待条件;若干进程之间形成一种头尾相接的循环等待资源关系
死锁案例:
public class DeadLock {
public static void main(String[] args) {
//创建线程;
MakeUp mu1=new MakeUp("甲",0);
MakeUp mu2=new MakeUp("乙",1);
//启动线程;
mu1.start();
mu2.start();
}
}
//口红类;
class Lipstic{ }
//镜子类
class Mirror{ }
//化妆类;
class MakeUp extends Thread {
//共享资源;
static Lipstic lipstic = new Lipstic();
static Mirror mirror = new Mirror();
//使用者;
String name;
//选择;
int choice;
//构造方法;
MakeUp(String name, int choice) {
this.name = name;
this.choice = choice;
}
//重写run方法;
@Override
public void run() {
try {
//化妆方法;
makeMeth();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//化妆方法;
public void makeMeth() throws InterruptedException {
if(choice==0){
//获得口红的锁;
synchronized (lipstic){
System.out.println(this.name+"获得口红锁");
//线程设置休眠;
Thread.sleep(1000);
//获得镜子的锁;
synchronized (mirror){
System.out.println(this.name+"获得镜子锁");
}
}
}else {
//获得口红的锁;
synchronized (mirror){
System.out.println(this.name+"获得镜子锁");
//线程设置休眠;
Thread.sleep(2000);
//获得镜子的锁;
synchronized (lipstic){
System.out.println(this.name+"获得口红锁");
}
}
}
}
}
两人都占有对方的锁
修改一下案例,不让同时占有两个锁;
public class DeadLock2 {
public static void main(String[] args) {
//创建线程;
MakeUp mu1=new MakeUp("甲",0);
MakeUp mu2=new MakeUp("乙",1);
//启动线程;
mu1.start();
mu2.start();
}
}
//口红类;
class Lipstic{ }
//镜子类
class Mirror{ }
//化妆类;
class MakeUp extends Thread {
//共享资源;
static Lipstic lipstic = new Lipstic();
static Mirror mirror = new Mirror();
//使用者;
String name;
//选择;
int choice;
//构造方法;
MakeUp(String name, int choice) {
this.name = name;
this.choice = choice;
}
//重写run方法;
@Override
public void run() {
try {
//化妆方法;
makeMeth();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//化妆方法;
public void makeMeth() throws InterruptedException {
if (choice == 0) {
//获得口红的锁;
synchronized (lipstic) {
System.out.println(this.name + "获得口红锁");
//线程设置休眠;
Thread.sleep(1000);
}
//获得镜子的锁;
synchronized (mirror) {
System.out.println(this.name + "获得镜子锁");
}
} else {
//获得口红的锁;
synchronized (mirror) {
System.out.println(this.name + "获得镜子锁");
//线程设置休眠;
Thread.sleep(2000);
}
//获得镜子的锁;
synchronized (lipstic) {
System.out.println(this.name + "获得口红锁");
}
}
}
}
即可正常运行
9.Lock锁
从jdk5开始,java提供了显式定义同步锁的对象实现同步,使用Lock对象作为同步锁;
java.util.concurrent.locks.Lock接口控制多个线程对于共享资源进行访问;
锁提供了对于共享资源的独占访问,每次只有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得的Lock对象;
ReentrantLock(可重入锁)类实现了Lock,和synchronized一样,有并发性和内存语义;在实现线程安全的控制中,常用的是ReentrantLock,可显式加锁,释放锁.
一般将加锁lock()
放入try{ }代码块中;解锁unlock
放在finally{ }代码块中(即使出现异常,也能释放锁);
synchronized与Lock
(1)Lock是显式锁,手动开启关闭,而synchronized是隐式锁,出了作用域就自动释放锁;
(2)Lock只有代码块锁,而synchronized还有方法锁;
(3)使用Lock锁,JVM将花费较少的时间来调度线程,性能更好,扩展性好
(4)优先顺序:Lock > 同步代码块 >同步方法
练习;买票案例(加入Lock锁)
public class LockDemo {
public static void main(String[] args) {
//创建实现类对象
BuyTicket by=new BuyTicket();
//创建线程后启动;
new Thread(by,"阿猫").start();
new Thread(by,"阿狗").start();
}
}
//买票
class BuyTicket implements Runnable{
//定义票数;
int ticket=10;
//定义锁;
private final ReentrantLock lock=new ReentrantLock();
//重写的run方法;包含线程执行体;
@Override
public void run() {
try{
//加锁;
lock.lock();
while(true){
if(ticket>0){
System.out.println(Thread.currentThread().getName()+"买票"+ticket--);
try {
//设置线程休眠
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
break;
}
}
}finally {
//解锁:
lock.unlock();
}
}
}
这里加锁后,不会出现两个人拿到同一张票.
10.生产者消费者问题
- 假设仓库只能放一件产品,生产者可以将生产品放入仓库,消费者可以取出产品;
- 假设仓库没货了,那么生产者就要补货;仓库的货一直没人取,那么生产者就会停止生产,进入等待;
- 若仓库有货,消费者可以取货,若没有,那么消费者就停止消费,进入等待
分析:
该问题涉及到线程同步,生产者与消费者共享一个资源,且两者之间相互依赖,互为条件;
在该问题中,若只是使用synchronized还远远不够,
synchronized可阻止并发更新同一个共享资源,实现同步,但不能用来实现不同线程之间的消息通信
线程通信常用方法;
属于Object类方法,只能用在同步方法或同步代码块中,否则抛出异常IllegalMonitorStateException;
方法 | 说明 |
---|---|
wait( ) | 表示线程一直等待,直到其他线程通知,与sleep方法不同的是,该方法可释放锁 |
wait(long time) | 设置等待的时间(毫秒) |
notify() | 唤醒一个处于等待状态的线程 |
notifyAll() | 唤醒同一个对象上所有调用了wait方法的线程 |
解决方式1:管程法
引入一个缓冲区,这个缓冲区可以设定大小;
生产者将产品存入缓冲区,消费者在缓冲区取产品;
代码实现
public class DemoPC {
//主方法;
public static void main(String[] args) {
//新建容器对象;
SyncBuffer syncBuffer=new SyncBuffer();
//创建线程,让生产者与消费者线程启动;
new Producer(syncBuffer).start();
new Consumer(syncBuffer).start();
}
}
//生产者;
class Producer extends Thread{
//定义缓冲区作为属性;
SyncBuffer syncBuffer;
//构造方法;
public Producer(SyncBuffer syncBuffer) {
this.syncBuffer = syncBuffer;
}
//生产;
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("生产编号为=>"+i+"的鸡----->");
syncBuffer.push(new Chicken(i));
}
}
}
//消费者
class Consumer extends Thread{
//定义缓冲区作为属性;
SyncBuffer syncBuffer;
//构造方法;
public Consumer(SyncBuffer syncBuffer){
this.syncBuffer = syncBuffer;
}
//消费;
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("<----消费编号为=>"+syncBuffer.pop().id+"的鸡");
}
}
}
//产品
class Chicken{
//产品编号;
int id;
//初始化构造方法;
public Chicken(int id) {
this.id = id;
}
}
//缓冲区;作为容器
class SyncBuffer{
//定义容量大小;
Chicken[] chickens=new Chicken[10];
//定义容器计数器;
int count=0;
//生产者放产品;
public synchronized void push(Chicken chicken){
//若容器满了就等待消费者消费;
if(count==chickens.length){
try {
//生产者进入等待;
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//若容器不满,则继续生产;
chickens[count]=chicken;
count++;
//通知消费者消费;
this.notifyAll();
}
//消费者取产品;
public synchronized Chicken pop(){
//判断还有没有产品;
if(count==0){
try {
//消费者进入等待;
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//正常消费;
count--;
Chicken chicken=chickens[count];
//通知生产者生产;
this.notifyAll();
return chicken;
}
}
解决方式2:信号灯法
设置一个标记(布尔值类型),在生产和消费时分别进行设置标记的值;
public class DemoPC02 {
//主方法
public static void main(String[] args) {
//创建柜台对象;
Counter counter=new Counter();
//创建生产者,消费者线程,并且启动;
new Producer(counter).start();
new Consumer(counter).start();
}
}
//生产者;
class Producer extends Thread{
//定义柜台属性;
Counter counter;
//构造方法;
public Producer(Counter counter) {
this.counter = counter;
}
//在这里调用生产方法;
@Override
public void run() {
for (int i = 1; i < 101; i++) {
if(i%2==0){
counter.shengChan("#白开水#编号"+i);
}else {
counter.shengChan("#可口可乐#编号"+i);
}
}
}
}
//消费者
class Consumer extends Thread{
//定义柜台属性;
Counter counter;
//构造方法;
public Consumer(Counter counter) {
this.counter = counter;
}
//在这里调用消费方法;
@Override
public void run() {
for (int i = 1; i < 101; i++) {
counter.xiaoFei();
}
}
}
//柜台
class Counter {
//生产者生产,消费者等待;---> true;
//消费者消费,生产者等待;---> false;
//产品-->水;
String Water;
//设置的标志位;
boolean flag=true;
//生产水;
public synchronized void shengChan(String water){
//当标志位为false,就让生产者线程等待;
if(!flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("生产了==>"+water);
//通知消费者线程;(唤醒);
this.notifyAll();
//更新水;
this.Water=water;
//改变标志位;
this.flag=!this.flag;
}
//消费;
public synchronized void xiaoFei(){
//当标志位为true;就让消费者线程等待;
if(flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("<==消费:"+Water);
//通知生产者线程;(唤醒);
this.notifyAll();
//改变标志位;
this.flag=!this.flag;
}
}
生产什么就消费什么
11.创建线程之使用线程池
将提前创建的多个线程,放入线程池中,使用时直接获取,用完再放回线程池;避免了频繁创建销毁,实现了重复利用;
提高了响应资源;减少创建新线程的时间;
降低资源消耗,重复利用线程池中的线程,不需要每次都创建;
便于线程管理;
corePoolSize:核心池的大小;
maximunPoolSize:最大线程数;
keepAliveTime:线程没有任务时,最多保持的时间就终止
从JDK5开始,API出现线程池: ExecutorService ,Executors;
- ExecutorService;线程池接口,常用子类 ; ThreadPoolExecutor
- void execute(Runnable cmd):执行任务/命令,
无返回值
,常用执行Runnable; <T>Future<T> submit(Callable<T> task)
;执行任务,有返回值,常用执行Callable
- void execute(Runnable cmd):执行任务/命令,
- Executors:工具类,作为线程池的工厂类;用户创建并返回不同类型的线程池
练习
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MyThreadPool {
public static void main(String[] args) {
//newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
ExecutorService executorService= Executors.newFixedThreadPool(15);
//执行线程
executorService.execute(new MyThread());
executorService.execute(new MyThread());
executorService.execute(new MyThread());
executorService.execute(new MyThread());
executorService.execute(new MyThread());
//关闭服务;
executorService.shutdownNow();
}
}
//自定义线程类;
class MyThread implements Runnable{
@Override
public void run() {
//Thread.currentThread().getName():线程名;
System.out.println(Thread.currentThread().getName());
}
}