即,并发就是你洗完澡后再听歌,并行就是你一边洗澡一边听歌。
(线程<进程)
一个程序运行后至少有一个进程,一个进程中可以包含多个线程。
举例:Word的使用。
每次打开一个Word就相当于启动了一个进程,
在这个进程上又有许多其他程序在执行(例如:拼写检查,自动更正等),这些就是一个个线程。
如果Word关闭了,这些线程就会全部消失。但是如果这些线程消失了,Word不一定会消失。
线程一定得依附于进程才能够存在。
“同时”执行是线程给人的感觉,在线程之间实际上是轮换执行。
设置线程的优先级:
打开任务管理器>选择希望设置优先级的进程>右键>转到详细信息>设置优先级
在Java中要想实现多线程的程序,必须依靠一个线程的主体类,即主线程(执行主方法(main)的线程)。然后此类继承Thread
类或实现Runnable
接口。
java.lang.Thread
是操作线程的类,任何类只需要继承Thread
类就可以成为一个线程的主类。
Thread类下的两个重要方法:run()
和start()
方法。
//1.创建一个Thread类的子类
public class MyThread extends Thread{
//2.重写Thread类中的run方法,设置线程任务
@Override
public void run(){
for (int i = 0; i < 20; i++) {
System.out.println("run:"+i);
}
}
}
public class doMain {
public static void main(String[] args) {
//3.创建Thread类的子类对象
MyThread mt = new MyThread();
//4.调用Thread类中的start(),开启新的线程,执行run()
mt.start();
for (int i = 0; i < 20; i++) {
System.out.println("main:"+i);
}
}
}
Thread类的常用方法:
getName() | 取得线程名字 |
---|---|
setName() | 设置线程名字 |
currentThread() | 取得线程名字 |
sleep(long millitime) | 使当前正在执行的程序以指定的毫秒数暂定 |
为了避免单继承局限的问题,我们可以使用Runnable
接口来实现多线程。
要启动多线程,就一定需要通过Thread
类中的start()
方法,但是Runnable接口中没有提供可以被继承的start()方法
。这时就需要借住Thread
类中提供的有参构造方法:
**public Thread(Runnable target) ** // 此方法可以接收一个Runnable接口对象
// 创建一个Runnable的实现接口类
public class RunnableImpl implements Runnable{
// 重写run方法,设置线程任务
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+"RunnableImpl");
}
}
}
public class RunnableDemo {
public static void main(String[] args) {
// 创建一个Runnable接口实现类对象
RunnableImpl run = new RunnableImpl();
Thread thread = new Thread(run);
thread.start();
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+">--"+"a");
}
}
}
①避免单继承局限;
②降低程序的耦合性,方便解耦。即把设置线程任务(实现类中重写run())和开启新线程(Thread类对象调用start())进行了分离。
●它们的实现都需要一个线程的主类,都必须在子类中覆写run()
方法,都必须调用Thread
类中的start()
方法来开启线程。
●Thread
类是Runnable
接口的子类,而使用Runnable接口可以避免单继承局限,方便解耦,并且可以更加方便地实现数据共享的概念。
public class Thread extends Object implements Runnable
●它们的结构:
Runnable接口 | Thread类 |
---|---|
class MyThread implements Runnable{} | class MyThread extends Thread{} |
new Thread(mt).start(); | mt.start(); |
ublic class doMain {
public static void main(String[] args) {
//1.线程的父类是Thread
new Thread(){
@Override
public void run(){
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+">--"+"a");
}
}
}.start();
//2.线程的接口Runnable
new Thread(new Runnable(){
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+"-->"+"b");
}
}
}).start();
}
}
先来解释不同步遇到的问题:
如果分成三个窗口卖100张票,假如不同步的话,就有可能出现三个窗口卖重票、错票的情况。(多个线程操作同一资源可能出现的情况,因为前面的线程还没完成操作,其它线程也进来操作车票。(抢占))
实现三个窗口来卖票的程序:
//实现卖票程序
public class RunnableImpl1 implements Runnable{
//定义一个多线程共享的票源
private int ticket = 10;
//设置线程任务:卖票
@Override
public void run() {
//先判断票是否存在
while(true){
if (ticket>0) {
//提高安全问题出现概率
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//票存在,卖票
System.out.println(Thread.currentThread().getName() + "->>正在卖第" + ticket + "张票");
ticket--;
}
}
}
}
public class doMain {
public static void main(String[] args) {
RunnableImpl1 run = new RunnableImpl1();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
}
}
运行结果:
Thread-1->>正在卖第10张票
Thread-0->>正在卖第10张票
Thread-2->>正在卖第10张票
Thread-2->>正在卖第7张票
Thread-1->>正在卖第7张票
Thread-0->>正在卖第7张票
Thread-1->>正在卖第4张票
Thread-0->>正在卖第4张票
Thread-2->>正在卖第4张票
Thread-1->>正在卖第1张票
Thread-2->>正在卖第1张票
Thread-0->>正在卖第1张票
解决方法:通过同步操作来解决
同步操作:一个代码块中的多个操作在同一时间段内只能由一个线程进行,其他线程要等待此线程完成后才可以继续执行。
synchronized(this){
//需要被同步操作的代码
}
关于this的解释:
使用同步代码块完成同步操作: 主要有变化的在run()方法里,doMain类不变。
注意
//实现卖票程序
public class RunnableImpl1 implements Runnable{
//定义一个多线程共享的票源
private int ticket = 100;
//设置线程任务:卖票
@Override
public void run() {
while(true){
//先判断票是否存在
synchronized(this){
if (ticket>0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//票存在,卖票
System.out.println(Thread.currentThread().getName() + "->>正在卖第" + ticket + "张票");
ticket--;
}else {
break;
}
}
}
}
}
public class doMain {
public static void main(String[] args) {
RunnableImpl1 run = new RunnableImpl1();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
}
}
利用synchronized定义的方法。
//实现卖票程序
public class RunnableImpl1 implements Runnable{
//定义一个多线程共享的票源
private int ticket = 100;
//设置线程任务:卖票
@Override
public void run() {
while(true){
sale();
}
}
public synchronized void sale(){
//先判断票是否存在
synchronized (this){
if (ticket>0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//票存在,卖票
System.out.println(Thread.currentThread().getName() + "->>正在卖第" + ticket + "张票");
ticket--;
}
}
}
}
补充:
java.util.concurrent.locks.Lock接口
Lock实现提供比使用synchronized方法和语句可以获得的更广泛的锁定操作。
Lock接口中的方法:
void Lock() | 获取锁 |
---|---|
void unlock() | 释放锁 |
Lock接口的实现类:ReentrantLock
java.util.concurrent.locks.ReentrantLock implements Lock
使用Lock锁完成同步操作:
三步走。1.创建ReentrantLock对象 2.获取锁 3.释放锁
//实现卖票程序
public class RunnableImpl1 implements Runnable{
//定义一个多线程共享的票源
private int ticket = 100;
//1.创建ReentrantLock对象
Lock lk = new ReentrantLock();
//设置线程任务:卖票
@Override
public void run() {
while(true){
//2.在可能出现线程安全的代码前调用Lock接口中的Lock方法获取锁.
lk.lock();
if (ticket>0) {
try {
Thread.sleep(10);
//票存在,卖票
System.out.println(Thread.currentThread().getName() + "->>正在卖第" + ticket + "张票");
ticket--;
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//3.在可能出现线程安全的代码后释放锁。
lk.unlock();
}
}
}
}
}
加入同步后明显比不加入同步慢许多,所以同步的代码性能低,但是数据安全性高。
同步和异步有什么区别。什么情况下使用?
如果一块数据要在多个线程间共享,则必须进行同步存取。当应用程序在对象上调用了一个需要花费很长时间来执行的方法,并且不希望让程序等待方法的返回时,那么就应该用异步编程,在很多情况下采用异步途径往往更有效率。
abstract的method是否可以同时是static,是否可以同时是native、synchronized?
method、static、native、synchronized都不能和“abstract”同时声明方法。
当一个线程进入一个对象的synchronized方法后,其他线程是否可访问此对象的其他方法?
不能访问,一个对象操作一个synchronized方法只能由一个线程访问。(其他线程等待)
死锁就是指两个线程都在等待彼此先完成,造成了程序的停滞状态。(过多的同步操作带来的问题)
举例说明:小张想要小李的画,小李想要小张的书,小张说你把书给我我就给你画,小李说你把画给我我就给你书。这样下去的结果可想而知,谁都得不到画/书。这实际上就是死锁的概念。
线程状态 | 导致状态发生条件 |
---|---|
NEW(新建) | 线程刚被创建,但是并未启动。还没调用start方法。 |
Runnable(可运行) | 线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操作系统处理器。 |
Blocked(锁阻塞) | 当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。 |
Waiting(无限等待) | 一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。 |
TimedWaiting(计时等待) | 同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep 、Object.wait。 |
Teminated(被终止) | 因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。 |
我们不需要去研究这几种状态的实现原理,我们只需知道在做线程操作中存在这样的状态。那我们怎么去理解这几个状态呢,新建与被终止还是很容易理解的,我们就研究一下线程从Runnable(可运行)状态与非运行状态之间的转换问题。
/*
等待唤醒案例:线程之间的通信
创建一个顾客线程(消费者):告知老板要的包子的种类和数量调用wait方法,放弃cpu的执行,进入到NWAITING状态(无限等待)
创建一个老板线程(生产者):花了5秒做包子,做好包子之后,调用notify方法,唤醒顾客吃包子
注意:
顾客和老板线程必须使用同步代码块包裏起来,保证等待和唤醒只能有一-个在执行
同步使用的锁对象必须保证唯一
只有锁对象才能调用wait和notify方法
Obejct类中的方法
void wait( )
在其他线程调用此对象的notify() 方法或notifyAll() 方法前,导致当前线程等待。
void notify()
唤醒在此对象监视器上等待的单个线程。
会继续执行wait方法之后的代码
*/
public class WaitingAndNotifyDemo {
public static void main(String[] args) {
// 创建锁对象,保证唯一
Object obj = new Object();
// 创建一个消费者线程
new Thread() {
@Override
public void run() {
// 保证等待和唤醒的线程只能由一个运行,需要使用同步技术
synchronized (obj) {
System.out.println("告知老板需要的包子种类和数量");
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 唤醒之后执行的代码
System.out.println("我的包子终于做好了,可以吃了");
}
}
}.start();
// 创建一个老板线程
new Thread() {
@Override
public void run() {
// 花费5s做包子
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj) {
System.out.println("老板做好了包子");
// 调用notify()方法,唤醒用户的等待状态,让他吃包子
obj.notify();
}
}
}.start();
}
}
调用wait和notify方法需要注意的细节
生产者和消费者是指两个不同的线程类对象,操作同一资源的情况。(即生产一个,取走一个)
由于牵扯到线程的不确定性,所以会存在以下两点问题:
假设生产者线程还没向数据存储空间添加完所有信息,程序就切入到了消费者线程,消费者线程将把该消息的名称和上一个信息的内容联系到一起。(不同步所造成)
生产者放了若干次的线程,消费者才开始取数据,或者是消费者取完一个数据后,还没等到生产者放入新的数据,又重复取出已取过的数据。
我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题:
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。
那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务?在Java中可以通过线程池来达到这样的效果。今天我们就来详细讲解一下Java的线程池。
线程池:其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。
由于线程池中有很多操作都是与优化资源相关的,我们在这里就不多赘述。我们通过一张图来了解线程池的工作原理:
合理利用线程池能够带来三个好处:
Java里面线程池的顶级接口是 java.util.concurrent.Executor
,但是严格意义上讲 Executor
并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是java.util.concurrent.ExecutorService
。
要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,很有可能配置的线程池不是较优的,因此在 java.util.concurrent.Executors
线程工厂类里面提供了一些静态工厂,生成一些常用的线程池。官方建议使用Executors
工程类来创建线程池对象。
Executors类中有个创建线程池的方法如下:
public static ExecutorService newFixedThreadPool(int nThreads)
:返回线程池对象。(创建的是有界线程池,也就是池中的线程个数可以指定最大数量)获取到了一个线程池ExecutorService 对象,那么怎么使用呢,在这里定义了一个使用线程池对象的方法如下:
public Future<?> submit(Runnable task)
:获取线程池中的某一个线程对象,并执行Future接口:用来记录线程任务执行完毕后产生的结果。线程池创建与使用。
使用线程池中线程对象的步骤:
1.使用线程池的工厂类Executors里边提供的静态方法newFixedThreadPool生产一个指定线程数量的线程池
2.创建一个类,实现Runnable接口,重写run方法,设置线程任务
3.调用ExecutorService中的方法submit,传递线程任务(实现类),开启线程,执行run方法
4.调用ExecutorService中的方法shutdown销毁线程池(不建议执行)
import java.util.concurrent.Executors;
public class Demo01ThreadPool {
public static void main(String[] args) {
//1.使用线程池的工厂类Executors里边提供的静态方法newFixedThreadPool生产一个指定线程数量的线程池
ExecutorService es = Executors.newFixedThreadPool(2);
//3.调用ExecutorService中的方法submit,传递线程任务(实现类),开启线程,执行run方法
//线程池会一直开启,使用完了线程,会自动把线程归还给线程池,线程可以继续使用
es.submit(new RunnableImpl());//pool-1-thread-1创建了一个新的线程执行
es.submit(new RunnableImpl());//pool-1-thread-1创建了一个新的线程执行
es.submit(new RunnableImpl());//pool-1-thread-2创建了一个新的线程执行
//4.调用ExecutorService中的方法shutdown销毁线程池(不建议执行)
es.shutdown();
es.submit(new RunnableImpl());//抛异常,线程池都没有了,就不能获取线程了
}
}
public class RunnableImpl implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"创建了一个新的线程执行");
}
}
原文:https://www.cnblogs.com/tiantian152/p/14495362.html