**
线程
**
在说线程之前,有必要提一下什么是进程。进程是具有一个独立功能的程序关于某个数据集合上的一次运行活动,是系统进行资源分配和调度的一个单独单位。
而线程是进程的一个实体,是CPU调度和分派的基本单位,他是比进程更小的能独立运行的基本单位,线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),一个线程可以创建和撤销另一个线程。
进程和线程的关系:
1.一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。
2.资源分配给进程,同一进程的所有线程共享该进程的所有资源。
3.线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。
4.处理机分给线程,即真正在处理机上运行的是线程。
5.线程是指进程内的一个执行单元,也是进程内的可调度实体
进程和线程的一个重要区别是操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。
下面我们看一下在Java中,如何实现多线程(三种方式)
第一种方式步骤:
1.创建Thread类的子类
2.重写run方法(设置线程任务)
3.创建Thread子类对象
4.调用start方法
Thread类的子类:
public class MyThread extends Thread {
public void run()
{
for(int i=0;i<10;i++)
{
System.out.println("run"+i);//线程任务
}
}
主函数
public static void main(String[] args) {
MyThread mt=new MyThread();
mt.start();//开启刚刚设置的线程
for(int i=0;i<10;i++)
{
System.out.println("main"+i);
}
}
结果
run0
run1
run2
run3
run4
run5
run6
main0
run7
main1
run8
main2
run9
main3
main4
main5
main6
main7
main8
main9
这个程序运行的时候有两个线程,main线程(主线程)和执行run方法的线程,两个线程交替运行(无序)。在看到结果后,多线程程序的原理也就比较好理解了:
1.JVM执行main方法,开辟一条mian方法到CPU的路径,这个路径称为主线程
2.JVM执行run方法,开辟一条run方法到CPU的路径
3.CPU有两条路径,按照随机的方法来选择一个执行
图解
我们现在看一下多线程的第二种实现方式
第二种方式步骤:
1.创建Runnable接口的实现类
2.重写run方法(设置线程任务)
3.创建Runnable的实现类的对象
4.创建Thread对象,传递Runnable实现类对象为构造方法的参数
5.调用start方法
Runnable接口的实现类:
public class MyRunnableImpl implements Runnable {
@Override
public void run() {
Thread t=Thread.currentThread();//currentThread方法
System.out.println(t);
String name=t.getName();//getName方法
System.out.println(name);
}
}
主函数:
public static void main(String[] args) {
MyRunnableImpl impl=new MyRunnableImpl();
Thread mt2=new Thread(impl);
mt2.start();
}
结果
Thread[Thread-0,5,main]
Thread-0
使用第二种方法的好处:
1.避免了单继承的局限性。一个类实现了接口之后还可以继承其他的类,实现其他的接口
2.增强了程序的扩展性,降低了程序的耦合性(解耦)。
把设置线程任务(实现类的run方法)和开启新的线程进行了分离
第三种方法其实就是利用匿名内部类,原理与上面两种方法相同,只是简化了写法。
public static void main(String[] args) {
//第三种多线程实现方法,使用匿名内部类
new Thread() {
@Override
public void run() {
Thread t=Thread.currentThread();//currentThread方法
System.out.println(t);
String name=t.getName();//getName方法
System.out.println(name);
}
}.start();
//第三种方法的第二种写法
Runnable r=new Runnable() {
@Override
public void run() {
Thread t=Thread.currentThread();//currentThread方法
System.out.println(t);
String name=t.getName();//getName方法
System.out.println(name);
}
};
new Thread(r).start();
}
结果:
Thread[Thread-0,5,main]
Thread-0
Thread[Thread-1,5,main]
Thread-1
线程池
多线程虽然可以提高运算效率,但是并发的线程数量很多,每一个线程执行完任务后就结束了,这样频繁创建线程会大大降低系统的效率。
线程池其实就是一个容纳多线程的容器,其中线程可以反复使用,省去了频繁创造线程对象的操作。
线程池的使用步骤:
1.使用线程池工厂类Executor里面的静态方法newFixedThreadPool生产一个指定线程数量的线程池(该方法返回ExecutorService接口的实现类对象)
2.创建一个类,实现Runnable接口,重写run方法,设置线程任务
3.调用ExecutorService的submit方法(传递第二步的对象),传递线程任务,开启线程,执行run方法
4.调用ExecutorService中的方法shutdown销毁线程池(执行了这个线程池就无法复用)
代码实现
public class Thread_Poor_priatice {
public static void main(String[] args) {
ExecutorService es=Executors.newFixedThreadPool(2);//第一步,这个线程池中有两个线程
es.submit(new RunnableInterfaceImpl());
es.submit(new RunnableInterfaceImpl());
es.submit(new RunnableInterfaceImpl());
es.submit(new RunnableInterfaceImpl());//第三步
//在从线程池中使用完线程后归还,再次拿出有可能还是这个线程
es.shutdown();//第四步(不建议执行)
}
}
Runable接口的实现类
public class RunnableInterfaceImpl implements Runnable{//第二步
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
结果
pool-1-thread-1
pool-1-thread-2
pool-1-thread-2
pool-1-thread-2
可以看到第一个任务是线程池中第一个线程执行的,后面的三个任务都是线程池中第二个线程执行的,但这完全是随机的。
多线程的临界区问题
临界区就是一种公共资源或者共享数据,可以被多个线程使用,但是每一次只能有一个线程使用它。一旦临界区资源被占用,那么其他线程必须等待。
假设有一家电影院有多个售票窗口,而电影票有100张,这就造成了临界区问题,我们来看一下为什么临界区问题困扰我们。
Runnable接口的实现类
public class RunnableInterfaceImpl2 implements Runnable {
private int ticket=100
@Override
public void run() {
while(ticket>0) {
System.out.println("线程"+Thread.currentThread().getName()+"正在卖第"+ticket+"张票");
ticket--;
}
}
}
主函数开启多线程买票
public static void main(String[] args) {
RunnableInterfaceImpl2 r1=new RunnableInterfaceImpl2();
Thread t1=new Thread(r1);
Thread t2=new Thread(r1);
Thread t3=new Thread(r1);
t1.start();
t2.start();
t3.start();
}
结果
线程Thread-2正在卖第100张票
线程Thread-2正在卖第99张票
线程Thread-2正在卖第98张票
线程Thread-2正在卖第97张票
线程Thread-2正在卖第96张票
线程Thread-2正在卖第95张票
线程Thread-2正在卖第94张票
线程Thread-2正在卖第93张票
线程Thread-2正在卖第92张票
线程Thread-2正在卖第91张票
线程Thread-2正在卖第90张票
线程Thread-2正在卖第89张票
线程Thread-2正在卖第88张票
线程Thread-2正在卖第87张票
线程Thread-2正在卖第86张票
线程Thread-2正在卖第85张票
线程Thread-0正在卖第100张票
线程Thread-2正在卖第84张票
线程Thread-2正在卖第83张票
线程Thread-2正在卖第82张票
线程Thread-2正在卖第81张票
线程Thread-2正在卖第80张票
线程Thread-2正在卖第79张票
线程Thread-2正在卖第78张票
线程Thread-2正在卖第77张票
线程Thread-2正在卖第76张票
线程Thread-2正在卖第75张票
线程Thread-2正在卖第74张票
线程Thread-2正在卖第73张票
线程Thread-2正在卖第72张票
线程Thread-2正在卖第71张票
线程Thread-2正在卖第70张票
线程Thread-2正在卖第69张票
线程Thread-0正在卖第68张票
线程Thread-0正在卖第66张票
线程Thread-0正在卖第65张票
线程Thread-0正在卖第64张票
线程Thread-0正在卖第63张票
线程Thread-0正在卖第62张票
线程Thread-0正在卖第61张票
线程Thread-0正在卖第60张票
线程Thread-0正在卖第59张票
线程Thread-0正在卖第58张票
线程Thread-0正在卖第57张票
线程Thread-0正在卖第56张票
线程Thread-0正在卖第55张票
线程Thread-0正在卖第54张票
线程Thread-0正在卖第53张票
线程Thread-0正在卖第52张票
线程Thread-0正在卖第51张票
线程Thread-0正在卖第50张票
线程Thread-0正在卖第49张票
线程Thread-0正在卖第48张票
线程Thread-0正在卖第47张票
线程Thread-0正在卖第46张票
线程Thread-0正在卖第45张票
线程Thread-0正在卖第44张票
线程Thread-0正在卖第43张票
线程Thread-0正在卖第42张票
线程Thread-0正在卖第41张票
线程Thread-0正在卖第40张票
线程Thread-0正在卖第39张票
线程Thread-0正在卖第38张票
线程Thread-0正在卖第37张票
线程Thread-0正在卖第36张票
线程Thread-0正在卖第35张票
线程Thread-0正在卖第34张票
线程Thread-0正在卖第33张票
线程Thread-0正在卖第32张票
线程Thread-0正在卖第31张票
线程Thread-0正在卖第30张票
线程Thread-0正在卖第29张票
线程Thread-0正在卖第28张票
线程Thread-0正在卖第27张票
线程Thread-0正在卖第26张票
线程Thread-0正在卖第25张票
线程Thread-0正在卖第24张票
线程Thread-0正在卖第23张票
线程Thread-0正在卖第22张票
线程Thread-0正在卖第21张票
线程Thread-0正在卖第20张票
线程Thread-0正在卖第19张票
线程Thread-0正在卖第18张票
线程Thread-0正在卖第17张票
线程Thread-0正在卖第16张票
线程Thread-0正在卖第15张票
线程Thread-0正在卖第14张票
线程Thread-0正在卖第13张票
线程Thread-0正在卖第12张票
线程Thread-0正在卖第11张票
线程Thread-0正在卖第10张票
线程Thread-0正在卖第9张票
线程Thread-0正在卖第8张票
线程Thread-0正在卖第7张票
线程Thread-0正在卖第6张票
线程Thread-0正在卖第5张票
线程Thread-0正在卖第4张票
线程Thread-0正在卖第3张票
线程Thread-0正在卖第2张票
线程Thread-0正在卖第1张票
线程Thread-1正在卖第86张票
线程Thread-2正在卖第67张票
我们可以看到有的票被卖了两次,这就是临界区问题导致的共享资源被同时修改后的结果。
这里提供了解决临界区问题的三种方法
第一种方法,使用synchronized同步代码块:
public class RunnableInterfaceImpl2 implements Runnable {
private int ticket=100;
Object obj=new Object();//创建一个锁对象,其实就是this
@Override
public void run() {
synchronized(obj) {
while(ticket>0) {
System.out.println("线程"+Thread.currentThread().getName()+"正在卖第"+ticket+"张票");
ticket--;}
}
}
}
主函数开启多线程来买票
public static void main(String[] args) {
RunnableInterfaceImpl2 r1=new RunnableInterfaceImpl2();
Thread t1=new Thread(r1);
Thread t2=new Thread(r1);
Thread t3=new Thread(r1);
t1.start();
t2.start();
t3.start();
}
结果
线程Thread-0正在卖第100张票
线程Thread-0正在卖第99张票
线程Thread-0正在卖第98张票
线程Thread-0正在卖第97张票
线程Thread-0正在卖第96张票
线程Thread-0正在卖第95张票
线程Thread-0正在卖第94张票
线程Thread-0正在卖第93张票
线程Thread-0正在卖第92张票
线程Thread-0正在卖第91张票
线程Thread-0正在卖第90张票
线程Thread-0正在卖第89张票
线程Thread-0正在卖第88张票
线程Thread-0正在卖第87张票
线程Thread-0正在卖第86张票
线程Thread-0正在卖第85张票
线程Thread-0正在卖第84张票
线程Thread-0正在卖第83张票
线程Thread-0正在卖第82张票
线程Thread-0正在卖第81张票
线程Thread-0正在卖第80张票
线程Thread-0正在卖第79张票
线程Thread-0正在卖第78张票
线程Thread-0正在卖第77张票
线程Thread-0正在卖第76张票
线程Thread-0正在卖第75张票
线程Thread-0正在卖第74张票
线程Thread-0正在卖第73张票
线程Thread-0正在卖第72张票
线程Thread-0正在卖第71张票
线程Thread-0正在卖第70张票
线程Thread-0正在卖第69张票
线程Thread-0正在卖第68张票
线程Thread-0正在卖第67张票
线程Thread-0正在卖第66张票
线程Thread-0正在卖第65张票
线程Thread-0正在卖第64张票
线程Thread-0正在卖第63张票
线程Thread-0正在卖第62张票
线程Thread-0正在卖第61张票
线程Thread-0正在卖第60张票
线程Thread-0正在卖第59张票
线程Thread-0正在卖第58张票
线程Thread-0正在卖第57张票
线程Thread-0正在卖第56张票
线程Thread-0正在卖第55张票
线程Thread-0正在卖第54张票
线程Thread-0正在卖第53张票
线程Thread-0正在卖第52张票
线程Thread-0正在卖第51张票
线程Thread-0正在卖第50张票
线程Thread-0正在卖第49张票
线程Thread-0正在卖第48张票
线程Thread-0正在卖第47张票
线程Thread-0正在卖第46张票
线程Thread-0正在卖第45张票
线程Thread-0正在卖第44张票
线程Thread-0正在卖第43张票
线程Thread-0正在卖第42张票
线程Thread-0正在卖第41张票
线程Thread-0正在卖第40张票
线程Thread-0正在卖第39张票
线程Thread-0正在卖第38张票
线程Thread-0正在卖第37张票
线程Thread-0正在卖第36张票
线程Thread-0正在卖第35张票
线程Thread-0正在卖第34张票
线程Thread-0正在卖第33张票
线程Thread-0正在卖第32张票
线程Thread-0正在卖第31张票
线程Thread-0正在卖第30张票
线程Thread-0正在卖第29张票
线程Thread-0正在卖第28张票
线程Thread-0正在卖第27张票
线程Thread-0正在卖第26张票
线程Thread-0正在卖第25张票
线程Thread-0正在卖第24张票
线程Thread-0正在卖第23张票
线程Thread-0正在卖第22张票
线程Thread-0正在卖第21张票
线程Thread-0正在卖第20张票
线程Thread-0正在卖第19张票
线程Thread-0正在卖第18张票
线程Thread-0正在卖第17张票
线程Thread-0正在卖第16张票
线程Thread-0正在卖第15张票
线程Thread-0正在卖第14张票
线程Thread-0正在卖第13张票
线程Thread-0正在卖第12张票
线程Thread-0正在卖第11张票
线程Thread-0正在卖第10张票
线程Thread-0正在卖第9张票
线程Thread-0正在卖第8张票
线程Thread-0正在卖第7张票
线程Thread-0正在卖第6张票
线程Thread-0正在卖第5张票
线程Thread-0正在卖第4张票
线程Thread-0正在卖第3张票
线程Thread-0正在卖第2张票
线程Thread-0正在卖第1张票
这样就保证了一张票只可以被卖一次,因为一次只可以有一个线程来修改公共资源。
第二种方法,使用synchronized同步方法,在范围修饰符后面加上synchronized关键字,方法中写的是共享的代码:
public class RunnableInterfaceImpl3 implements Runnable {
private int ticket=100;
@Override
public synchronized void run() {
while(ticket>0) {
System.out.println("线程"+Thread.currentThread().getName()+"正在卖第"+ticket+"张票");
ticket--;
}
}
}
run方法的使用和打印的结果都是一样的,这里就不展示了。
第三种方法,使用Lock锁(Lock是一个接口,我们要使用它的一个实现类叫ReentrantLock),在进入公共资源区域前加锁,出公共资源区域后释放锁:
public class RunnableInterfaceImpl4 implements Runnable {
ReentrantLock lock=new ReentrantLock();//锁对象必须定义在方法外面
private int ticket=100;
@Override
public void run() {
lock.lock();//上锁
while(ticket>0) {
System.out.println("线程"+Thread.currentThread().getName()+"正在卖第"+ticket+"张票");
ticket--;
}
lock.unlock();//释放锁
}
}
这就是三种解决临界区问题的方法,下次更新线程之间的通讯。