线程Thread
Thread
内部类:
static class Thread.State //线程生命周期
每个线程都有优先级别:
static int MAX_PRIORITY 10
线程可以拥有的最大优先级。
static int MIN_PRIORITY 1
线程可以拥有的最小优先级。
static int NORM_PRIORITY 5
分配给线程的默认优先级。
理论上级别越大,先被执行的概率越大,具体得看cpu
常用构造:
Thread(Runnable target) //分配一个新的 Thread对象。
Thread(Runnable target, String name) //每个线程都有名字
Thread(String name)
常用方法:
static Thread currentThread() //返回对当前正在执行的线程对象的引用
long getId() //返回此线程的标识符,自动生成,从1开始
String getName() //返回此线程的名称。
int getPriority() //返回此线程的优先级。
Thread.State getState() //获得线程的状态
void setName(String name) //将此线程的名称更改为等于参数 name 。
void setPriority(int newPriority) //更改此线程的优先级。
void join() //等待这个线程死亡
void join(long millis) //在指定时间millis内优先执行当前线程
void run() //当前线程的运行逻辑都在run里面(禁止调用)
static void sleep(long millis) //使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)超时继续执行下一个功能
void start() //导致此线程开始执行; Java虚拟机调用此线程的run方法
static void yield() //礼让,对调度程序的一个暗示,即当前线程愿意产生当前使用的处理器
//下面方法必须在同步中使用,并且需要监视器Monitor
void wait() //当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法
void wait(long timeout) //当前线程等待,直到另一个线程调用 notify()方法或该对象的 notifyAll()方法,或者指定的时间已过
void notify() //唤醒正在wait对象监视器的单个线程。
void notifyAll() //唤醒正在等待对象监视器的所有线程
设置线程休眠的时候一般都是用TimeUnit枚举来进行设置:
public static void main(String[] args) {
Thread thread = Thread.currentThread();
try {
Thread.sleep(1000);//不推荐这种方式,一般都用下面的枚举来进行设置休眠
TimeUnit.SECONDS.sleep(12);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread.setName("main1");
System.out.println(thread);
System.out.println(thread.getPriority());
System.out.println(thread.getId());
System.out.println(thread.getState());
}
实现多线程的四种方式:
- 继承Thread 重写里面的run方法
public class ChildThread1 extends Thread {
@Override
public void run() {
//重写的方法体
}
}
-
实现Runnable接口,重写里面的run方法
public class ChildThread2 implements Runnable {
@Override
public void run() {
//重写的方法体
}
}
上面两种方法创建的线程的区别:
public class MainThread{
//下面我们利用sleep来对线程进行交替开始
public static void main(String[] args) {
//该类实现了runnable接口,跟thread相当于同级,因此需要给他放到thread构造中将他变为thread,这里底层使用了静态代理设计模式
ChildThread1 childThread1 = new ChildThread1();
new Thread(childThread1,"小红").start();
//直接继承thread接口里面会有start方法
new ChildThread2("小明").start();
}
}
//idea里面绿色启动按钮按了之后相当于启动了一个进程,会立即开启main,main线程里面开启子线程xx的时候不会阻塞,会继续执行,且子线程和主线程是交替执行。
run和start的区别:
直接调用run方法,就会走完当前线程的run方法之后执行下一个线程的run方法,此时不是多线程,start会开启多线程
public synchronized void start(){
xxx;
start0();
//实现多线程的真正方法
xxx;
}
问题来了,如何在main中控制他的子线程呢?
public class Demo2 {
public static void main(String[] args) {
ChildThread1 childThread1 = new ChildThread1();
childThread1.start();
try {
//设置一秒后停止改线程
Thread.sleep(1000);
childThread1.setFlag(false);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class ChildThread1 extends Thread {
//为了让结果清晰一点,这个线程中设置一个变量并且输出
private Integer count=0;
//创建一个外部可以设置的控制变量
private boolean flag=true;
public void setFlag(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
while (flag){
System.out.println("count:"+count++);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
join方法:插队,插入某线程等到他死亡/指定多少秒再执行当前线程
public class Demo2 {
public static void main(String[] args) {
ChildThread1 childThread1 = new ChildThread1();
childThread1.start();
for (int i = 0; i < 100; i++) {
if(i==20) {
try {
childThread1.join();
//等待childThread1线程走完,再走当前线程
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(i);
}
}
}
class ChildThread1 extends Thread {
private Integer count=0;
@Override
public void run() {
String name=Thread.currentThread().getName();
while (count<30){
System.out.println(name+"-->count:"+count++);
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
守护线程:
public class Demo2 {
public static void main(String[] args) {
ChildThread1 childThread1 = new ChildThread1();
//将该线程设置为当前线程的守护线程,当前线程死亡,守护线程也随之结束
childThread1.setDaemon(true);
childThread1.start();
String name=Thread.currentThread().getName();
for (int i = 0; i < 10; i++) {
System.out.println(name+":"+i);
}
}
}
class ChildThread1 extends Thread {
private Integer count=0;
@Override
public void run() {
String name=Thread.currentThread().getName();
while (count<100){
System.out.println(name+"-->count:"+count++);
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
线程同步机制:
当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作。
1.加互斥锁synchronized,可以放在代码块,或者修饰方法
public class SellTickets {
//模拟售票
public static void main(String[] args) {
ChildRunnable childRunnable = new ChildRunnable();
new Thread(childRunnable, "pdd").start();
new Thread(childRunnable, "tb").start();
new Thread(childRunnable, "wx").start();
}
}
class ChildRunnable implements Runnable {
//总票数30
private int tickets = 30;
private boolean flag=true;
@Override
public void run() {
while (sell()){}
}
//加锁,当一个线程进入这个方法的时候,其他线程没法进去
private synchronized boolean sell() {
String name = Thread.currentThread().getName();
if (tickets < 1) {
System.out.println(name+": 票卖完啦");
return false;
}
System.out.println(name + "正在卖第" + (tickets--) + "张票,剩余票数"+tickets);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return true;
}
}
修饰代码块:利用this对象
private void sell() {
synchronized (this){
String name = Thread.currentThread().getName();
if (tickets < 1) {
System.out.println(name + ": 票卖完啦");
return;
}
System.out.println(name + "正在卖第" + (tickets--) + "张票,剩余票数" + tickets);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
锁静态方法:锁的对象应当为当前类.class
public static void sell() {
synchronized (ChildRunnable.class){
//想锁的内容
}
}
所个线程都占用了对方的锁资源,但不肯相让,会出现死锁,在编程中应当避免死锁的产生
实现callable接口 重写call方法,放入FutureTask
public class Competition {
public static void main(String[] args) {
//创建同一个比赛对象
Runner runner = new Runner();
//利用FutureTask来创建两个跑步对象,并且获取结果
FutureTask<String> rbResult = new FutureTask<>(runner);
//兔子开始跑步
new Thread(rbResult, "兔子").start();
FutureTask<String> ttResult = new FutureTask<>(runner);
//乌龟开始跑步
new Thread(ttResult, "乌龟").start();
try {
//比赛结果已经出来了,下面两个输出的结果是一样的
String rbName = rbResult.get();
String ttName = ttResult.get();
System.out.println(rbName);
System.out.println(ttName);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
class Runner implements Callable<String> {
//比赛的胜利者
private String winner;
@Override
public String call() throws Exception {
String name = Thread.currentThread().getName();
for (int i = 1; i <= 100; i++) {
System.out.println(name + "跑了" + i + "米");
//如果胜利者不为空,停止
if (winner != null) break;
}
//跑到100米停止,并且获取胜利者
if (winner == null)
winner = name;
return winner;
}
}
创建n个线程,并发下载小说
下载类:
public class Down {
private Down() {
}
public static void downLoad(String sour, String dest) {
File destPath=new File(dest);
try (
BufferedReader reader = new BufferedReader(new InputStreamReader(new URL(sour).openStream()));
BufferedWriter writer = new BufferedWriter(new FileWriter(destPath))
) {
String s;
while ((s=reader.readLine())!=null){
writer.write(s);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
下载线程类:
public class DownNovelThread extends Thread {
private String sourFile;
private String destFile;
public DownNovelThread(String sourFile, String destFile) {
this.sourFile = sourFile;
this.destFile = destFile;
}
@Override
public void run() {
Down.downLoad(sourFile,destFile);
}
}
开启多线程:
private static void demo2() {
String[] novels = {
"https://read.qidian.com/chapter/p36QbYfOfTVdi5-cWpsVtA2/65O7zQiNyeW2uJcMpdsVgA2/",
"https://read.qidian.com/chapter/p36QbYfOfTVdi5-cWpsVtA2/ttY1dryd7P62uJcMpdsVgA2/",
"https://read.qidian.com/chapter/p36QbYfOfTVdi5-cWpsVtA2/eWetL7lvrXf4p8iEw--PPw2/",
"https://read.qidian.com/chapter/p36QbYfOfTVdi5-cWpsVtA2/3fOjFW-QgXuaGfXRMrUjdw2/",
"https://read.qidian.com/chapter/p36QbYfOfTVdi5-cWpsVtA2/65O7zQiNyeW2uJcMpdsVgA2/",
"https://read.qidian.com/chapter/p36QbYfOfTVdi5-cWpsVtA2/ttY1dryd7P62uJcMpdsVgA2/",
"https://read.qidian.com/chapter/p36QbYfOfTVdi5-cWpsVtA2/eWetL7lvrXf4p8iEw--PPw2/",
"https://read.qidian.com/chapter/p36QbYfOfTVdi5-cWpsVtA2/65O7zQiNyeW2uJcMpdsVgA2/",
"https://read.qidian.com/chapter/p36QbYfOfTVdi5-cWpsVtA2/ttY1dryd7P62uJcMpdsVgA2/",
"https://read.qidian.com/chapter/p36QbYfOfTVdi5-cWpsVtA2/eWetL7lvrXf4p8iEw--PPw2/",
"https://read.qidian.com/chapter/p36QbYfOfTVdi5-cWpsVtA2/3fOjFW-QgXuaGfXRMrUjdw2/",
"https://read.qidian.com/chapter/p36QbYfOfTVdi5-cWpsVtA2/65O7zQiNyeW2uJcMpdsVgA2/",
"https://read.qidian.com/chapter/p36QbYfOfTVdi5-cWpsVtA2/ttY1dryd7P62uJcMpdsVgA2/",
"https://read.qidian.com/chapter/p36QbYfOfTVdi5-cWpsVtA2/eWetL7lvrXf4p8iEw--PPw2/",
"https://read.qidian.com/chapter/p36QbYfOfTVdi5-cWpsVtA2/3fOjFW-QgXuaGfXRMrUjdw2/"
};
List<Thread> threads=new ArrayList<>(10);
for (int i = 0; i < novels.length; i++) {
String destPath="resource/"+(i+1)+".txt";
DownNovelThread thread=new DownNovelThread(novels[i],destPath);
threads.add(thread);
}
threads.forEach(Thread::start);
}
解决线程安全的方案
并行 vs 并发:
都是在同一时间做多个任务
并行:同一时刻,多个任务同时执行,多核cpu可以实现并行
并发:同一时刻多个任务交替执行,造成一种貌似同时的错觉,简单地说,单核cpu实现的多任务就是并发---------> parallel
用户级别的并发:高并发、高性能、高可用———> concurrent
对于tomcat:最高200个并发,mysql:最高151个并发
同步 vs 异步
同步:一个新任务的开始必须等待另外一个任务的结束 容易出现线程阻塞
异步:一个任务的开始不需要等待另外一个任务结束 ajax(异步刷新页面)
阻塞 vs 非阻塞
同步必然会出现阻塞
输入流就是一种阻塞(阻塞IO:BIO)不输入就无法继续下面程序
BIO:同步阻塞IO
NIO:同步非阻塞IO
AIO:异步非阻塞IO
进程 vs 线程
进程:一个应用软件就是一个进程 占据CPU和内存,当有一个线程死掉了,整个进程就死掉了
线程:比进程小的执行单元:一个进程里面有多个线程单元。线程与线程之间是相互独立的—->独有的线程栈,线程之间的通信更加方便,jvm默认至少两个线程:main 线程、GC守护线程
同步非阻塞: 多个线程同时执行 不会出现阻塞
同步阻塞: 多个线程都不做任务
异布阻塞: 多个线程并发执行:都阻塞
异步非阻塞:多个线程并发执行:都正常执行