创建线程
Java使用 java.lang.Thread 类代表线程,所有的线程对象都必须是Thread类或其子类的实例。
Thread类
public class MyThread extends Thread{
public MyThread(String name) {
//调用父类的String参数的构造方法,指定线程的名称
super(name);
}
/**
* 重写run方法,完成该线程执行的逻辑
*/
@Override
public void run() {
try {
sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
for(int i = 0; i < 100; i++) {
System.out.println(getName() + i);
}
}
}
public class ThreadTest {
public static void main(String[] args) {
//创建自定义线程对象
MyThread mt1 = new MyThread("a");
MyThread mt2 = new MyThread("b");
MyThread mt3 = new MyThread("c");
//开启新线程
mt1.start();
mt2.start();
mt3.start();
//主线程中的for循环
for (int i = 0; i < 20; i++) {
System.out.println("主线程:"+i);
}
}
}
程序启动运行main时候,java虚拟机启动一个进程,主线程main在main()调用时候被创建。随着调用mt的对象的 start方法,另外三个新的线程也启动了,这样,整个应用就在多线程下运行。
Runnable接口
public class MyRunnable implements Runnable{
@Override
public void run() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
for(int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + i);
}
}
}
public class RunableTest {
public static void main(String[] args) {
MyRunnable mr = new MyRunnable();
new Thread(mr, "a").start();
new Thread(mr, "b").start();
new Thread(mr, "c").start();
}
}
通过实现Runnable接口,使得该类有了多线程类的特征。run()方法是多线程程序的一个执行目标。所有的多线程 代码都在run方法里面。Thread类实际上也是实现了Runnable接口的类。
在启动的多线程的时候,需要先通过Thread类的构造方法Thread(Runnable target) 构造出对象,然后调用Thread 对象的start()方法来运行多线程代码。
实际上所有的多线程代码都是通过运行Thread的start()方法来运行的。因此,不管是继承Thread类还是实现 Runnable接口来实现多线程,最终还是通过Thread的对象的API来控制线程的,熟悉Thread类的API是进行多线程 编程的基础。
实现Runnable接口比继承Thread类所具有的优势
- 适合多个相同的程序代码的线程去共享同一个资源
- 可以避免java中的单继承的局限性
- 增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立
- 线程池只能放入实现Runable或Callable类线程,不能直接放入继承Thread的类。
在java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。因为每当使用 java命令执行一个类的时候,实际上都会启动一个JVM,每一个JVM其实在就是在操作系统中启动了一个进程。
线程安全
如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。程序每次运行结果和单线程运行的结果是一样 的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
在多线程场景下,如果没有对资源加锁,会导致共享资源的访问出错,下面模拟售票演示线程不安全:
public class Ticket implements Runnable {
private int ticket = 100;
@Override
public void run() {
while (true) {
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
String name = Thread.currentThread().getName();
System.out.println(name + "正在卖:" + ticket);
ticket--;
}
}
}
}
public class Test1 {
public static void main(String[] args) {
Ticket ticket = new Ticket();
Thread t1 = new Thread(ticket, "窗口1");
Thread t2 = new Thread(ticket, "窗口2");
Thread t3 = new Thread(ticket, "窗口3");
t1.start();
t2.start();
t3.start();
}
}
部分结果:
线程同步
当我们使用多个线程访问同一资源的时候,且多个线程中对资源有写操作,就容易出现线程安全问题。
为了保证每个线程都能正常执行原子操作,Java引入了线程同步机制。有三种方式完成同步操作:
- 同步代码块
- 同步方法
- 锁机制
同步代码块
synchronized
关键字可以用于方法中的某个区块中,表示只对这个区块的资源进行互斥访问。
synchronized(同步锁) {
需要同步操作的代码
}
同步锁是一个抽象的概念,锁对象可以是任意类型,比如new Object()也可以。
public class Ticket implements Runnable {
private int ticket = 100;
// 一个对象,作为一把锁
Object obj = new Object();
@Override
public void run() {
while (true) {
// 也可以直接用this作为锁对象
synchronized (obj) {
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
String name = Thread.currentThread().getName();
System.out.println(name + "正在卖:" + ticket);
ticket--;
}
}
}
}
}
同步方法
使用synchronized修饰的方法叫做同步方法,保证一个线程在执行方法的时候,其他线程阻塞等待。
public synchronized void method() {
可能产生线程安全问题的代码
}
public class Ticket implements Runnable {
private int ticket = 100;
@Override
public void run() {
while (true) {
payTicket();
}
}
// 锁对象默认就是this
public synchronized void payTicket() {
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
String name = Thread.currentThread().getName();
System.out.println(name + "正在卖:" + ticket);
ticket--;
}
}
}
同步方法payTicket()也可以设置为静态的,对于非静态方法,同步锁是this,对于静态方法,使用当前方法所在类的字节码对象(类名.class)。
Lock锁
Lock锁也称同步锁
- public void lock() :加同步锁
- public void unlock() :释放同步锁
public class Ticket implements Runnable {
private int ticket = 100;
Lock l = new ReentrantLock();
@Override
public void run() {
while (true) {
l.lock();
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
String name = Thread.currentThread().getName();
System.out.println(name + "正在卖:" + ticket);
ticket--;
l.unlock();
}
}
}
}
}
线程间通信
多个线程并发执行时,在默认情况下,CPU是随机性在线程之间进行切换,但是有时候我们希望它能够有规律的执行,那么多线程之间就需要一些协调通信来改变或控制CPU的随机性。Java提供了等待唤醒机制来解决这个问题,具体来说就是多个线程依靠一个同步锁,然后借助于wait()
和notify()
方法就可以实现线程间的协调通信。
同步锁相当于中间人的作用,多个线程必须用同一个同步锁,只有同一个锁上的被等待线程,才可以被持有该锁的另一个线程唤醒,使用不同锁的线程之间不能相互唤醒,也就是无法协调通信。
Java在Object类中提供了一些方法可以用来实现线程间的协调通信:
- public final void wait():释放当前线程的锁
- public final native void wait(long timeout):释放当前线程的锁,并等待timeout毫秒
- public final native notify():唤醒持有同一个锁的某个线程
- public final native notifyAll():唤醒持有同一个锁的所有线程
下面演示使用这几个API实现多线程情况下,线程有序执行,下面的例子就是循环利用的就是Java中的线程等待唤醒机制。下面的例子就是两个线程交替执行:
public class MyLock {
public static Object o = new Object();
}
public class ThreadFor1 extends Thread{
public void run() {
for(int i = 0; i < 10; i++) {
synchronized (MyLock.o) {
System.out.println("thread1->"+i);
// 先唤醒其他的一个线程
MyLock.o.notify();
try {
// 再等待
MyLock.o.wait();
}catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public class ThreadFor2 extends Thread{
public void run() {
for(int i = 0; i < 10; i++) {
synchronized (MyLock.o) {
System.out.println("thread2->"+i);
// 先唤醒其他的一个线程
MyLock.o.notify();
try {
// 再等待
MyLock.o.wait();
}catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public class Test {
public static void main(String[] args) {
Thread t1 = new ThreadFor1();
Thread t2 = new ThreadFor2();
t1.start();
t2.start();
}
}