Java 之多线程
- 进程
- 线程
- 多线程存在的意义
- 线程的创建方式
- 多线程的特性
进程: 正在运行中的程序.打开一个软件, 相当于打开了一个进程.
线程
- 线程, 就是进程中一个负责程序执行的控制单元(执行路径).
- 一个进程中至少要有一个线程, 也可以有多个执行路径(即多线程).
- 开启多线程是为了同时运行多部分代码
- 每一个线程都有自己运行的内容, 这个内容可以称为线程要执行的任务.
- 多线程优缺点
- 好处: 解决了多部分同时运行的问题
- 弊端: 线程太多会造成效率的降低
其实应用程序的执行都是 cpu 在做着快速的切换完成的, 这个切换是随机的.
JVM 线程分析
JVM 启动时, 就启动了多个线程, 至少有两个线程可以分析的出来:
- 执行 main 函数的线程. 该线程的任务代码都定义在 main 函数中.
- 负责垃圾回收的线程
创建线程
- 创建线程的目的是为了开启一条执行路径, 可以让指定的代码和其他代码实现同时运行.
其中, JVM 创建的主线程的任务都定义在主函数中. - 自定义的线程的任务是通过 Thread 类中的 run 方法来体现的. 也就是说, run 方法就是封装
自定义线程运行任务的函数. Thread 类用于描述线程, 线程是需要任务的, 而 run() 方法中定义
的就是线程要运行的任务代码. - 开启线程是为了运行指定代码, 所以只有继承 Thread 类, 并复写 run() 方法, 将运行的代码定义
在 run() 方法中即可.
创建线程的方式一: 继承 Thread 类
步骤:
- 定义一个类继承 Thread 类
- 覆盖 Thread 类中的 run() 方法
- 直接创建 Thread 类的子类对象,即创建线程
- 调用 start() 方法开启线程并调用线程的任务 run 方法执行.
class Demo extends Thread
{
private String name;
Demo(String name)
{
this.name = name;
}
public void run()
{
show();
}
public void show()
{
for(int x=0; x<10; x++)
{
for(int y=-999999; y<999999; y++){}
System.out.println(name + "...x="+x);
}
}
}
class ThreadDemo
{
public static void main(String[] args)
{
Demo d1 = new Demo("旺财"); // 一创建线程, 该线程就有编号
Demo d2 = new Demo("xiaoqiang");
d1.start(); //开启线程, Java 虚拟机调用该线程的 run 方法
d2.start();
}
}
Thread 类中的方法
- getName(), 获取线程的名称, Thread-编号(从 0 开始)
- currentThread(), 静态方法, 获取当前执行线程对象.
通过Thread.currentThread().getName()
获取当前运行线程
线程的四种状态
创建线程的方式二: 实现 Runnable 接口
步骤:
- 定义类实现 Runnable 接口
- 覆盖接口中的 run() 方法, 将线程的任务代码封装到 run() 方法中
- 通过 Thread 类创建线程对象, 并将 Runnable 接口的子类对象作为 Thread 类的构造函数的
参数进行传递. 因为线程的任务都封装在 Runnable 接口子类对象的 run 方法中, 所以要在线程对象
创建时就必须明确要运行的任务. - 调用线程对象的 start 方法开启线程
实现 Runnable 接口的好处:
- 将线程的任务从线程的子类中分离出来, 进行了单独的封装.
按照面向对象的思想将任务封装成对象. - 避免了 java 单继承的局限性.
// 示例: 卖票
class Ticket implements Runnable
{
private int num = 100;
public void run()
{
sale();
}
public void sale()
{
while(true)
{
if(num>0)
{
System.out.println(Thread.currentThread().getName()+"......"+num--);
}
}
}
}
class TicketDemo
{
public static void main(String[] args)
{
Ticket t = new Ticket(); // 创建一个线程任务对象
Thread t1 = new Thread(t); // 创建线程
Thread t2 = new Thread(t); // 创建线程
t1.start();
t2.start();
}
}
线程安全问题
线程安全问题产生的原因:
- 多个线程在操作共享的数据
- 操作共享数据的代码有多条
- 当一个线程在执行操作共享数据的多条代码过程中, 其他线程参与了运算, 就会导致线程安全问题.
解决思路: 就是将多条操作共享数据的线程代码封装起来, 当有线程在执行这些代码的时候,
其他线程是不可以参与运算的, 只有当前线程把这些代码都执行完毕后, 其他线程才可以参与运算.
同步代码块
在 java 中, 用同步代码块就可以解决这个问题.
格式:
synchronized(对象) //此处对象相当于锁
{
需要被同步的代码;
}
// 同步代码块
class Ticket implements Runnable
{
private int num = 100;
Object obj = new Object();
public void run()
{
sale();
}
public void sale()
{
while(true)
{
synchronized(obj) // 同步代码块, 对象 obj 相当于锁
{
if(num>0)
{
System.out.println(Thread.currentThread().getName()+"......"+num--);
}
}
}
}
}
- 同步的好处: 解决了线程的安全问题
- 同步的弊端: 相对于以前降低了效率, 因为同步外的线程都会判断同步锁.
- 同步的前提: 同步中必须有多个线程并使用同一个锁.
同步函数
// 需求: 两个储户, 往一个帐号存钱, 每次存 100, 各存 3 次
class Bank
{
private int sum;
/* private Object obj = new Object();
public void add(int num)
{
synchronized(obj) //此处存在安全隐患, 使用同步代码块
{
sum = sum + num;
System.out.println("sum="+sum);
}
}
*/
// 同步函数
public synchronized void add(int num)
{
sum = sum + num;
System.out.println("sum="+sum);
}
}
class Cus implements Runnable
{
private Bank b = new Bank();
public void run()
{
for(int x=0; x<3; x++)
{
b.add(100);
}
}
}
class BankDemo
{
public static void main(String[] args)
{
Cus c = new Cus();
Thread t1 = new Thread(c);
Thread t2 = new Thread(c);
t1.start();
t2.start();
}
}
- 同步函数使用的锁是 this
- 同步函数和同步代码块的区别
- 同步函数的锁是固定的, this
- 同步代码块的锁是任意的对象
- 建议使用同步代码块
- 静态同步函数时的锁是该函数所属字节码文件对象(Class), 可以用 getClass 方法获取,
也可以用 当前类名.class 表示. 因为静态函数中没有 this
多线程下的单例
// 饿汉式
class Single
{
private static Single s = new Single();
private Single(){}
public static Single getInstance()
{
return s;
}
}
// 懒汉式
class Single
{
private static Single s = null;
private Single(){}
public static Single getInstance()
{
if(s == null) // 此处存在安全隐患
s = new Single();
return s;
}
}
// 懒汉式升级, 线程安全
class Single
{
private static Single s = null;
private Single(){}
public static Single getInstance()
{
if(s==null) //此处为提高效率, 线程判断锁次数减少
{
synchronized(Single.class) // 注意 静态方法,
{
if(s==null)
s = new Single();
}
}
return s;
}
}
死锁
class Test implements Runnable
{
private boolean flag;
Test(boolean flag)
{
this.flag = flag;
}
public void run()
{
if(flag)
{
synchronized(MyLock.locka)
{
System.out.println("if locka....");
synchronized(MyLock.lockb)
{
System.out.println("if lockb....");
}
}
}
else
{
synchronized(MyLock.lockb)
{
System.out.println("else lockb....");
synchronized(MyLock.locka)
{
System.out.println("else locka....");
}
}
}
}
}
class MyLock
{
public static final Object locka = new Object();
public static final Object lockb = new Object();
}
class DeadLockTest
{
public static void main(String[] args)
{
Test a = new Test(true);
Test b = new Test(false);
Thread t1 = new Thread(a);
Thread t2 = new Thread(b);
t1.start();
t2.start();
}
}
参考资料