目录
多线程的两组概念
一、程序、进程、线程
程序:静态代码
进程:正在运行的实现某个独立功能的程序,它是操作系统进行资源分配和调度的一个独立单位。进程间不能共享资源。
线程:进程可以细化为线程,它不能单独运行,必须在一个进程环境中运行。
例如,一段java程序运行时,整个运行的程序称为进程,此进程中有main主线程、垃圾回收线程、异常处理线程等,而这些线程要随着进程而运行,不能独立运行。这时,如果我运行一段matlab程序,就是另外一个进程了。
二、 并行、并发
并发(concurrency):把多个任务在不同的时间段交给单个处理器进行处理。在同一时间点,任务并不会同时运行。 即,并发是多个任务分别占据单个处理器的部分时间片段,处理任务。
并行(parallelism):把多个任务分别分配给多个处理器独立完成。在同一时间点,任务一定是同时运行。并行是真正的多线程,并发是宏观上的多线程,是一种“假的”并行。
线程的创建(基本)
方式一:继承于Thread类
//1、创建一个继承于Thread类的子类
public class ThreadCreateWay1 extends Thread{
public static void main(String[] args) {
//3、创建该子类的对象,调用start()方法启动线程
new ThreadCreateWay1().start();
//同理,启动第二条线程
new ThreadCreateWay1().start();
}
//2、重写Thread类中的run()方法,把该线程要执行的代码写进去
@Override
public void run() {
super.run();
System.out.println("创建了一条线程");
}
}
方式二:实现Runnable接口
//1、创建一个实现了Runnable接口的实现类
public class ThreadCreateWay2 implements Runnable{
public static void main(String[] args) {
//3、创建该实现类的对象
ThreadCreateWay1 threadcreat = new ThreadCreateWay1();
//4、将实现类的对象作为参数传递到Thread类的构造器中,创建Thread类的对象,调用start()方法启动线程
new Thread(threadcreat).start();
//启动第二个线程
new Thread(threadcreat).start();
}
//2、实现Runnable接口中的抽象方法run()
@Override
public void run() {
System.out.println("创建了一条线程");
}
}
多线程的同步
方式一、二都要用到java中的一个关键字synchornized。
方式一:同步代码块
//声明在重写(实现)的run()方法中
sychornized(同步监视器){
//需要被同步的代码:操作共享数据的代码
}
方式二:同步方法
如果操作共享数据的代码完整的声明在一个方法中,可将此方法声明为同步的。
如:
public sychornized void show(){...}
以上两个方法在运用时都有一个很重要的问题就是同步监视器的选取。
同步监视器(锁)的要求是:多个线程必须共用一把锁。这样才能实现线程的同步,解决线程安全问题。
在同步代码块中,在保证上述要求的情况下,任何一个类的对象都可以作为同步监视器。 需要注意的是,在实现Runnable接口创建多线程的情况下,由于创建多条线程时,只需创建实现类的一个对象,故可以使用this作为同步监视器,可以保证多个线程共用一把锁。而在继承Thread类创建多线程的情况下,由于创建多条线程时,需要创建多个子类对象,故不能使用this作为同步监视器,因为多个子类对象会导致多条线程使用的锁不统一。此时可考虑使用当前类(×××.class)充当同步监视器。
在同步方法中,在实现Runnable接口创建多线程的情况下,虽然没有显式地声明同步监视器,但实际有同步监视器,为this。同理,在继承Thread类创建多线程的情况下,为了保证多个线程共用一把锁,子类中的同步方法必须声明为static的,该方法只随类的加载而加载一次,从而保证多个线程共用一把锁。
方式三:Lock锁
如果说sychornized关键字是自动的加锁和释放锁,Lock就是手动加锁释放锁了。Lock是一个接口,运用时首先实例化Lock的实现类ReentrantLock,再在合适的位置调用锁定方法lock()和解锁方法unlock()就可以实现线程同步了。
举例
模拟3个窗口卖100张票。
如果不加锁,会出现一张票被卖多次的情况:
//售票窗口线程
public class TicketWindowThread implements Runnable{
//定义100张票,票号:1-100
private int ticketsum = 100;
@Override
public void run() {
while(true){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(ticketsum > 0) {
System.out.println(Thread.currentThread().getName() + "售出:" + ticketsum + "号票...");
ticketsum--;
}else{
break;
}
}
}
}
public class TicketTest {
public static void main(String[] args) {
TicketWindowThread windows = new TicketWindowThread();
Thread window1 = new Thread(windows,"窗口1");
Thread window2 = new Thread(windows,"窗口2");
Thread window3 = new Thread(windows,"窗口3");
window1.start();
window2.start();
window3.start();
}
}
窗口2售出:100号票...
窗口1售出:100号票...
窗口3售出:100号票...
窗口3售出:97号票...
窗口1售出:97号票...
窗口2售出:97号票...
窗口3售出:94号票...
窗口1售出:94号票...
窗口2售出:94号票...
窗口1售出:91号票...
窗口2售出:91号票...
窗口3售出:91号票...
窗口3售出:88号票...
加锁后,这种线程不安全问题就被解决了。
//售票窗口线程
public class TicketWindowThread implements Runnable{
//定义100张票,票号:1-100
private int ticketsum = 100;
@Override
public void run() {
while(true){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (this){
if(ticketsum > 0) {
System.out.println(Thread.currentThread().getName() + "售出:" + ticketsum + "号票...");
ticketsum--;
}else{
break;
}
}
}
}
}
窗口2售出:100号票...
窗口1售出:99号票...
窗口3售出:98号票...
窗口3售出:97号票...
窗口2售出:96号票...
窗口1售出:95号票...
窗口3售出:94号票...
窗口1售出:93号票...
窗口2售出:92号票...
窗口3售出:91号票...
窗口2售出:90号票...