1.简介
一个进程可以有多个线程,例如打开一个手机app就是开了一个进程,在这个进程中可以使用多个线程;
线程就像是代码执行的路线,多线程就是多条代码可同时执行;例如百度搜索肯定是多线程的,不然只能一个人访问;在例如服务器的后台被客户端访问,肯定也是多线程处理客户端的请求的;迅雷下载也是多线程的;jvm也是多线程的,main线程和gc线程;
当然也有单线程的,例如:android的更新UI是单线程的
2.原理
- 并发:开启多个线程,但是CPU只有一个啊。其实CPU是在这个多个线程中来回的切换,便面上是同时执行的。
- 并行:除非是多核的CPU。所以多线程并行执行任务的话必须多核处理,才不会存在CPU在线程间切换;
3.实现:Thread
在java中Thread就是一个线程类
创建一个线程可以直接new Thread并实现Thread类中run方法;也可以传一个Runable接口进入,然后实现Runable接口中的run方法;
常用的方法:
getName();返回一个线程的名字,线程默认的编号,从0开始;注意这个名字不是线程的id;也可以在创建线程的时候传一名字,获取的就是这个名字;
currentThread();返回当前线程的对象;这是一个静态的方法;可以通过类名调用;
sleep();线程休眠,传一个时间进去,休眠时间;
join();当前线程暂停,等待指定线程执行结束的时候,当前线程再继续执行;就是插队,可以指定插队时间;
setPriority();设置线程的优先级;
wait();其实这个方法是Object的,所有的类都会有这个方法,那个线程调用了任何对象的wait()都会线程等待;
notify();这个方法也是Object的,所有的类都会有的,线程调用了任何对象的notify()都会唤醒等待监听器上的单个线程,如果有多个线程在等待的话,会随机唤醒一个;
notifyAll();这个方法也是Object的,所有的类都会有的,线程调用了任何对象的notify()都会唤醒等待监听器上的所有线程;
sleep();线程休眠,可以传入参数,休眠时间;
sleep和wait的区别:
- sleep必须传入参数,这个参数就是休眠的时间,过了时间线程就醒来了;wait可以传参也可以不传参,传参是过多少时间后休眠,不传参数的话就是立刻等待;
- 在同步方法和代码块中sleep不会释放锁子;但是wait会释放锁子;
4.线程同步;
线程并发的时候不想让cpu在执行某一块代码的时候cpu切换,使用的同步代码块;
public void hahah()
{
//这个this是同步的锁子,是任意的对象都是可以的;
synchronized (this)
{
System.out.print("aaa");
}
//代码执行到这里就吧锁子this释放了
}
public void gegege()
{
//这个this是同步的锁子,是任意的对象都是可以的;
//比如先执行的hahah(),当hahah中没有释放锁子的时候,cpu执行权切换到这个方法是,检查这个锁子是锁的
//这个方法被调用的线程就阻塞在这里了;直到hahah把锁子释放掉;
synchronized (this)
{
System.out.print("bbb");
}
//代码执行到这里就吧锁子this释放了
}
同步方法:
//当两个线程同事调用这个bbb方法的时候就要使用同步方法;
public synchronized void bbb()
{
System.out.print("bbb");
}
那么静态的同步锁是什么呢?是类的字节码对象
public void gegege()
{
//比如先执行的bbb(),当hahah中没有释放锁子的时候,cpu执行权切换到这个方法是,检查这个锁子是锁的
//这个方法被调用的线程就阻塞在这里了;直到bbb把锁子释放掉;
synchronized (test.class)
{
System.out.print("bbb");
}
//代码执行到这里就吧锁子this释放了
}
//静态的方法,锁子是类的字节码对象test.class;
//当两个线程同事调用这个bbb方法的时候就要使用同步方法;
public static synchronized void bbb()
{
System.out.print("bbb");
}
死锁:两个线程中我获取你的锁,你获取我的锁就形成了死锁。是在同步代码块嵌套使用的时候出现的;为了避免死锁,不要使用同步代码块嵌套;
关于一些类的线程安全的信息:
上面的类在多线程使用的时候要注意安全性;Collections.synchroinzed();将不安全的ArrayList等传进去就可以变成安全的;
5.线程间通信(等待唤醒机制)
下面代码是一个小例子,开启两个线程使用线程等待和唤醒机制的方式交替打印两句话:
public static void t2() {
String[] content1 = new String[]{"a", "b", "c", "d","e", "f", "g", "h"};
String[] content2 = new String[]{"1", "2", "3", "4","5", "6", "7", "8"};
new Thread() {
@Override
public void run() {
for (int i = 0; i < content1.length; i++) {
synchronized (Test2.class) {
System.out.print(content1[i]);
Test2.class.notify();
try {
if (i < content1.length - 1) {
Test2.class.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}.start();
new Thread() {
@Override
public void run() {
for (int i = 0; i < content1.length; i++) {
synchronized (Test2.class) {
System.out.print(content2[i]);
Test2.class.notify();
try {
if (i < content2.length - 1) {
Test2.class.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}.start();
}
输出结果:a1b2c3d4e5f6g7h8
要用test.class.wait()和test.class.notify;用什么对象锁,就用什么对象去调用wait和notify;这句是为什么这个两个方法定义在Object中,因为锁子是任意对象,Object是任意类的基类;
wait() 和 同步代码块执行完成后,这两种情况会释放锁子;
6.在JDK1.5新特性中出现了互斥锁(ReentrantLock)
在reentrantLock类中有
lock();是获取锁子;
unLock()是释放之前获取的锁子;
newCondition(); 获取线程的监视器;返回一个Condition对象;在Condition中有await和signal方法等待和唤醒线程;
可以唤醒指定的线程,不像notify随机唤醒;
这个样就可以从新设计上面交替打印的例子了:
public static void t() {
ReentrantLock lock = new ReentrantLock();
Condition c1 = lock.newCondition();
Condition c2 = lock.newCondition();
LinkedList<Integer> queue = new LinkedList<>();
String[] content1 = new String[]{"a", "b", "c", "d","e", "f", "g", "h"};
String[] content2 = new String[]{"1", "2", "3", "4","5", "6", "7", "8"};
new Thread() {
@Override
public void run() {
for (int i = 0; i < content1.length; i++) {
lock.lock();
try {
System.out.println(content1[i]);
c2.signal();
if (i < content1.length - 1) {
c1.await();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.unlock();
}
}
}.start();
new Thread() {
@Override
public void run() {
for (int i = 0; i < content1.length; i++) {
lock.lock();
try {
System.out.println(content2[i]);
c1.signal();
if (i < content1.length - 1) {
c2.await();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.unlock();
}
}
}.start();
}
7.线程组
比如在主线程中有开启了两个线程,那么这两个线程就默认是主线程组中的线程。当然也可以创建一个线程组,然后在创建多个线程添加到这个线程组中去,有些操作可以一组一组的去做;例如下面设置这个线程组为守护线程
8.线程的5种状态
9.线程池
程序启动一个新线程成本是比较高的,因为它涉及到要与操作系统进行交互。而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑使用线程池。线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。在JDK5之前,我们必须手动实现自己的线程池,从JDK5开始,Java内置支持线程池。
Exectors.newFixedThreadPool(int size):创建一个固定大小的线程池。 每来一个任务创建一个线程,当线程数量为size将会停止创建。当线程池中的线程已满,继续提交任务,如果有空闲线程那么空闲线程去执行任务,否则将任务添加到一个无界的等待队列中。
ExecutorService executorService = Executors.newFixedThreadPool(4);//创建一个池子,里面有4个线程
executorService.submit(new Runnable());//执行一个任务
executorService.shutdown();//关闭线程池
Exectors.newCachedThreadPool():创建一个可缓存的线程池。对线程池的规模没有限制,当线程池的当前规模超过处理需求时(比如线程池中有10个线程,而需要处理的任务只有5个),那么将回收空闲线程。当需求增加时则会添加新的线程。
Exectors.newSingleThreadExcutor():创建一个单线程的Executor,它创建单个工作者线程来执行任务,如果这个线程异常结束,它会创建另一个线程来代替。
Exectors.newScheduledThreadPool():创建一个固定长度的线程池,而且以延迟或定时的方式来执行任务。
上面都是通过工厂方法来创建线程池,其实它们内部都是通过创建ThreadPoolExector对象来创建线程池的。下面是ThreadPoolExctor的构造函数。