一、线程简介
任务:执行的目标,具体由线程来实现
进程:程序是指令和数据的有序集合,是一个静态的概念;进程是执行程序的一次执行过程,是一个动态的概念.(进程是系统资源分配的单位)
线程:一个进程至少有一个线程.线程是CPU调度和执行的单位
注:
多线程:多个线程的同时执行
二、线程创建的方式
Thread class:继承Thread类(其实也是实现了Runnable接口)
package com.guan.test;
public class ThreadTeast1 extends Thread{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("次线程" + i);
}
}
public static void main(String[] args) {
ThreadTeast1 threadTeast1 = new ThreadTeast1();
threadTeast1.start();
for (int i = 0; i < 1000; i++) {
System.out.println("主线程" + i);
}
}
}
注:线程的调度是有延时的,由cpu调度执行.所以这里的主函数的循环建议设为1000(具体由电脑的特性决定),否则无法清晰地看到主线程的输出将次线程的输出包裹
延伸:利用多线程下载图片
导入相关的依赖:
<!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
编写主程序:
package com.guan.test;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
public class ThreadTest2 extends Thread{
private String url;
private String name;
public ThreadTest2(){
}
public ThreadTest2(String url,String name){
this.url = url;
this.name = name;
}
@Override
public void run() {
PictureDownloader downloader = new PictureDownloader();
try {
downloader.pictureDownloader(this.url,this.name);
System.out.println("下载完成:" + name);
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
ThreadTest2 threadTest1 = new ThreadTest2("xxx1.png","xxx1.png");
ThreadTest2 threadTest2 = new ThreadTest2("xxx2.jpg","xxx2.jpg");
ThreadTest2 threadTest3 = new ThreadTest2("xxx3.png","xxx3.png");
threadTest1.start();
threadTest2.start();
threadTest3.start();
}
}
class PictureDownloader{
public void pictureDownloader(String url,String name) throws IOException {
//调用了外部的图片下载工具
FileUtils.copyURLToFile(new URL(url),new File(name));
}
}
注:这里实现的一个有趣的细节,外部的数据需要作为Thread的实现类的属性放进去!
图片大小:1>3>2
现在完成速度:3>2>1
Runnable接口:实现Runnable接口
静态代理
定义类实现Runnable接口
实现run()方法,编写线程执行体
创建Thread对象,同时将Runnable实现类的对象丢进去,调用start()方法启动线程
package com.guan.test;
public class ThreadTest3 implements Runnable{
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("次线程" + i);
}
}
public static void main(String[] args) {
ThreadTest3 threadTest3 = new ThreadTest3();
new Thread(threadTest3).start();
for (int i = 0; i < 1000; i++) {
System.out.println("主线程" + i);
}
}
}
将上一个图片下载的类改成用Runnable实现
package com.guan.test;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
public class ThreadTest2 implements Runnable{
private String url;
private String name;
public ThreadTest2(){
}
public ThreadTest2(String url,String name){
this.url = url;
this.name = name;
}
public void run() {
PictureDownloader downloader = new PictureDownloader();
try {
downloader.pictureDownloader(this.url,this.name);
System.out.println("下载完成:" + name);
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
ThreadTest2 threadTest1 = new ThreadTest2("http://icwl.org.cn/images/sponsors1.png","sponsors1.png");
ThreadTest2 threadTest2 = new ThreadTest2("http://icwl.org.cn/images/sponsors2.jpg","sponsors2.jpg");
ThreadTest2 threadTest3 = new ThreadTest2("http://icwl.org.cn/images/sponsors3.png","sponsors3.png");
//主要是这里用了静态代理方式的实现
new Thread(threadTest1).start();
new Thread(threadTest2).start();
new Thread(threadTest3).start();
}
}
class PictureDownloader{
public void pictureDownloader(String url,String name) throws IOException {
FileUtils.copyURLToFile(new URL(url),new File(name));
}
}
静态代理的实现:(从三个对象说起)
优点:避免了单继承的局限性,灵活方便,同一个对象,可以被多个线程使用
缺点:在使用同一个对象的情况下,可能多个线程操作同一个资源造成线程不安全
package com.guan.test;
public class ThreadTest4 implements Runnable {
private int resources = 10;
public void run() {
while(true){
//获得当前线程的名字
System.out.println(Thread.currentThread().getName() + "抢到了第" + resources-- + "张票");
try {
//小明的线程第一个开启,为了防止他一下子抢完票,每次先睡0.2s
Thread.currentThread().sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (resources <=1){
break;
}
}
}
public static void main(String[] args) {
ThreadTest4 test4 = new ThreadTest4();
new Thread(test4,"小明").start();
new Thread(test4,"老师").start();
new Thread(test4,"黄牛党").start();
}
}
注:几个人之间很可能抢到相同的一张票
Callable接口:实现Callable接口
ExecutorService ser = Executors.newFixedThreadPool(1);
Future<Boolean> result1 = ser.submit(t1);
boolean r1 = result1.get();
ser.shutdownNow();
优点:
初次使用:(后面还会详细讲到Executor类)
package com.guan.test;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.*;
public class ThreadTest6 implements Callable<Boolean> {
private String url;
private String name;
public ThreadTest6(){
}
public ThreadTest6(String url,String name){
this.url = url;
this.name = name;
}
//该方法的返回值为布尔型
public Boolean call() {
PictureDownloader downloader = new PictureDownloader();
try {
downloader.pictureDownloader(this.url,this.name);
System.out.println("下载完成:" + name);
} catch (IOException e) {
e.printStackTrace();
return false;
}
return true;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
ThreadTest6 threadTest1 = new ThreadTest6("http://icwl.org.cn/images/sponsors1.png","sponsors1.png");
ThreadTest6 threadTest2 = new ThreadTest6("http://icwl.org.cn/images/sponsors2.jpg","sponsors2.jpg");
ThreadTest6 threadTest3 = new ThreadTest6("http://icwl.org.cn/images/sponsors3.png","sponsors3.png");
//建立连接池
ExecutorService ser = Executors.newFixedThreadPool(3); //可以创建的线程数量为3
//提交线程
Future<Boolean> result1 = ser.submit(threadTest1);
Future<Boolean> result2 = ser.submit(threadTest2);
Future<Boolean> result3 = ser.submit(threadTest3);
boolean r1 = result1.get();
boolean r2 = result2.get();
boolean r3 = result3.get();
System.out.println(r1);
System.out.println(r2);
System.out.println(r3);
//关闭连接池
ser.shutdownNow();
}
}
这里看一下Callable接口中的call方法
@FunctionalInterface
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
注:v是call的返回值.当我们实现这一接口时,需要将v的类型写在接口的附近(如上例中的返回类型就是Boolean):
public class ThreadTest6 implements Callable<Boolean>
三、Lambda表达式(函数式编程)
使用前提:函数式接口
函数式接口的定义:
优化过程:内部实现类->静态内部类->局部内部类->匿名内部类(没有类的名称,必须借助**接口**或者父类)->lambda表达式简化(可以简化参数类型,括号,花括号)
代码:
package com.guan.test;
public class ThreadTest7 {
//静态内部类
static class ILove2 implements Love{
public void ILove() {
System.out.println("I love 2");
}
}
public static void main(String[] args) {
//局部内部类
class ILove3 implements Love{
public void ILove() {
System.out.println("I love 3");
}
}
ILove1 iLove1 = new ILove1();
ILove2 iLove2 = new ILove2();
ILove3 iLove3 = new ILove3();
//匿名内部类
Love iLove4 = new Love(){
public void ILove() {
System.out.println("I love 4");
}
}; //这是个语句,所以需要分号作为结尾
//箭头函数
Love iLove5 = ()->{
System.out.println("I love 5");
};
iLove1.ILove();
iLove2.ILove();
iLove3.ILove();
iLove4.ILove();
iLove5.ILove();
}
}
//函数式接口
interface Love{
public abstract void ILove();
}
//内部实现类
class ILove1 implements Love{
public void ILove() {
System.out.println("I love 1");
}
}
优点:
应用场景:多线程中的Runnable接口
注:在idea中的使用需要进行一些设置,详细可见这篇文章https://blog.csdn.net/mtngt11/article/details/100052996
四、线程状态
状态:创建,就绪,运行,阻塞,死亡
相关方法:
停止线程:
建议线程正常停止:利用次数,不推荐死循环
建议使用一个标志位(flag),比如保持线程是在flag=true
的情况下才能运行
不要用过时或者JDK不建议使用的方法
package com.guan.test;
public class ThreadTest8 implements Runnable {
private Boolean flag = true;
@Override
public void run() {
int i=0;
while(flag){
System.out.println("次线程正在跑" + ++i);
}
}
public void stop(){
this.flag=false;
}
public static void main(String[] args) {
ThreadTest8 thread = new ThreadTest8();
new Thread(thread).start();
for (int i = 0; i < 1000; i++) {
System.out.println("主线程正在跑" + i);
if (i == 900){
thread.stop();
System.out.println("次线程停下了!");
}
}
}
}
线程休眠
特点:
作用:
模拟网络延时,放大问题的发生性
package com.guan.test;
public class ThreadTest4 implements Runnable {
private int resources = 10;
public void run() {
while(true){
System.out.println(Thread.currentThread().getName() + "抢到了第" + resources-- + "张票");
try {
//小明的线程第一个开启,为了防止他一下子抢完票,每次先睡0.2s
Thread.currentThread().sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (resources <=1){
break;
}
}
}
public static void main(String[] args) {
ThreadTest4 test4 = new ThreadTest4();
new Thread(test4,"小明").start();
new Thread(test4,"老师").start();
new Thread(test4,"黄牛党").start();
}
}
注:还是这个案例,如果不睡一下,所有票都会被小明抢到,因为小明这个线程是最先开启的,且抢票太快了.如果睡一下,又会发现线程是不安全的,因为有多个人很可能正好抢到了同一张票
模拟倒计时,打印当前时间(如果采用的不是主线程可能无法拿到控制权,因此这个方案是不太严谨的)
package com.guan.test;
import java.text.SimpleDateFormat;
import java.util.Date;
public class ThreadTest9 {
public static void TenDown() throws InterruptedException {
int now = 10;
while(now>0){
System.out.println(now--);
//这里相当于睡的是主线程
Thread.sleep(1000);
}
}
public static void logTime() throws InterruptedException {
while(true){
Date date = new Date(System.currentTimeMillis());
System.out.println(new SimpleDateFormat("HH:mm:ss").format(date));
Thread.sleep(1000);
}
}
public static void main(String[] args) throws InterruptedException {
ThreadTest9.TenDown();
}
}
线程休眠
特点:
礼让线程,让当前正在执行的线程暂停,但不阻塞
将线程从运行状态转为就绪状态
让cpu重新调度,礼让不一定成功,和具体的CPU调度算法有关
一个不太严谨的测试:
package com.guan.test;
public class ThreadTest10 implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "线程开始执行");
Thread.yield();
System.out.println(Thread.currentThread().getName() + "线程结束执行");
}
public static void main(String[] args) {
ThreadTest10 test10 = new ThreadTest10();
new Thread(test10,"a").start();
new Thread(test10,"b").start();
}
}
合并进程
特点:
Join合并继承,待此进程执行完成后,再执行其他线程,其他线程阻塞
package com.guan.test;
public class ThreadTest11 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("次线程在跑:" + i);
}
}
public static void main(String[] args) throws InterruptedException {
ThreadTest11 test11 = new ThreadTest11();
Thread thread = new Thread(test11);
thread.start();
for (int i = 0; i < 1000; i++) {
System.out.println("主线程在跑:" + i);
if(i==500){
//在副线程跑的过程中,让位给主线程跑
thread.join();
}
}
}
}
观测线程的状态
package com.guan.test;
public class ThreadTest12 implements Runnable{
@Override
public void run() {
System.out.println("线程开始了");
for (int i = 0; i < 5; i++) {
try {
// System.out.println("睡");
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("线程结束");
}
public static void main(String[] args) throws InterruptedException {
ThreadTest12 test12 = new ThreadTest12();
Thread thread = new Thread(test12);
Thread.State state = thread.getState();
System.out.println(state); //new
thread.start();
state = thread.getState(); //runnable
while (state != Thread.State.TERMINATED){
state = thread.getState(); //time_waiting
System.out.println(state);
Thread.sleep(100);
}
System.out.println(state); //terminated
thread.start();
}
}
注:
线程优先级
特点:Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有进程,线程调度器按照优先级决定应该调度哪个线程来执行
优先级范围:1~10
注意线程的实际执行还是要看cpu,但是优先级搞得线程执行的可能性更大
package com.guan.test;
public class ThreadTest13 extends Thread{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "-->" + Thread.currentThread().getPriority());
}
public static void main(String[] args) {
System.out.println("main --> " + Thread.currentThread().getPriority());
ThreadTest13 test1 = new ThreadTest13();
ThreadTest13 test2 = new ThreadTest13();
ThreadTest13 test3 = new ThreadTest13();
ThreadTest13 test4 = new ThreadTest13();
ThreadTest13 test5 = new ThreadTest13();
ThreadTest13 test6 = new ThreadTest13();
test1.setPriority(-1);
test2.setPriority(2);
test3.setPriority(4);
test4.setPriority(6);
test5.setPriority(8);
test6.setPriority(11);
test1.start();
test2.start();
test3.start();
test4.start();
test5.start();
test6.start();
}
}
注:先设置优先级再启动
守护线程(daemon)
特点:
代码:
package com.guan.test;
public class ThreadTest14 {
public static void main(String[] args) {
God god = new God();
People people = new People();
Thread thread = new Thread(god);
thread.setDaemon(true); //设置为守护线程
thread.start();
new Thread(people).start();
}
}
class God implements Runnable{
@Override
public void run() {
while(true){
System.out.println("God bless you!");
}
}
}
class People implements Runnable{
@Override
public void run() {
for (int i = 0; i < 36500; i++) {
System.out.println("活着的第" + i + "天");
}
}
}
注:可以看到虽然守护线程永远为true,但是在用户结束后它也会结束.简单来说就是把你送走了它才走
五、线程同步机制
并发:同一个对象被多个线程同时操作
线程同步:线程同步多用于处理多线程问题(通常为多个线程访问同一个对象).线程同步其实是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用
每个线程在自己的工作内存交互,内存控制不当会造成数据不一致(第一个线程的工作内存有3张票,第二个线程的工作内存有5张票,而实际上可能已经没有票了,这种情况下的售票显然会得到负值)
两个测试案例:
package com.guan.test;
public class ThreadTest15 implements Runnable{
private int num = 10;
private Boolean flag =true;
@Override
public void run() {
while(flag){
buy();
}
}
private void buy() {
if (num>0){
//在读与写之间增加延时可以放大效果
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "拿到了第" + num-- + "张票");
}else{
flag = false;
}
}
public static void main(String[] args) {
ThreadTest15 test = new ThreadTest15();
new Thread(test,"1").start();
new Thread(test,"2").start();
new Thread(test,"3").start();
}
}
package com.guan.test;
public class ThreadTest16 implements Runnable{
Acount acount;
int now;
int get;
public ThreadTest16(Acount acount, int now, int get) {
this.acount = acount;
this.now = now;
this.get = get;
}
@Override
public void run() {
if(acount.num-get>=0){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
acount.num -= get;
now += get;
System.out.println(Thread.currentThread().getName() + "现在有:" + now + ";acount现在还有" + acount.num);
}else{
System.out.println(Thread.currentThread().getName() + "取不出:" + get);
}
}
public static void main(String[] args) {
Acount acount = new Acount("基金", 100);
ThreadTest16 threadTest1 = new ThreadTest16(acount,100,50);
ThreadTest16 threadTest2 = new ThreadTest16(acount,100,100);
new Thread(threadTest1,"小明").start();
new Thread(threadTest2,"小红").start();
}
}
class Acount{
String name;
int num;
public Acount(String name, int num) {
this.name = name;
this.num = num;
}
}
/*
小明现在有:150;acount现在还有50
小红现在有:200;acount现在还有-50
原因:小明差->sleep->小红查->sleep->小明扣->小红扣
*/
/*
小红现在有:200;acount现在还有0
小明现在有:150;acount现在还有0
看上去很离谱的答案:小红查且取到100数据->小明查且取到100数据->小明付款且返回50->小红付款且返还0(覆盖50)->两个人都打印出了0(50同样的道理,虽然还是很离谱,但是仔细想想所有的程序都是要编译成小段的机器码执行也就稍微可以理解了)
*/
注:通过这里0/0,50/50的结果我们可以极大地感受到线程的不安全性
线程不安全的集合:
ArrayList (两个线程再同一时间覆盖了同一位置,倒置出现数据的缺失)
package com.guan.test;
import java.util.ArrayList;
public class ThreadTest17 {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
new Thread(()->{
list.add(3);
}).start(); }
System.out.println(list.size());
}
}
注:这里实际的list.size不会有1000,因为在线程运作的过程中很可能出现两个线程共同只能用同一个位置,从而导致结果错误
解决方案:同步方法——synchronized修饰符
本质:队列+锁
优点:安全
缺点:性能以及性能倒置问题(优先级高的线程等待优先级低的线程拿到的锁)
使用:
同步方法:修饰方法,但锁的是方法的this对象
package com.guan.test;
public class ThreadTest15 implements Runnable{
private int num = 10;
private Boolean flag =true;
@Override
public void run() {
while(flag){
buy();
}
}
private synchronized void buy() {
if (num>0){
//在读与写之间增加延时可以放大效果
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "拿到了第" + num-- + "张票");
}else{
flag = false;
}
}
public static void main(String[] args) {
ThreadTest15 test = new ThreadTest15();
new Thread(test,"1").start();
new Thread(test,"2").start();
new Thread(test,"3").start();
}
}
同步块:修饰语句块,锁的对象就是变化的量,需要增删改的对象
格式:synchronized(obj){}
注:obj是同步监视器,通常是共享资源,如果synchronized修饰的是方法,可以认为obj就是this
原文:https://www.cnblogs.com/Arno-vc/p/13637544.html