线程的实现
创建多线程的第一种方式:创建Thread类的子类
实现步骤:
1. 创建一个Thread类的子类
2. 在Thread类的子类中重写Thread的run方法,设置线程任务(开启线程要做什么?)
3. 创建Thread类的子类对象
4. 调用Thread类中的start方法,开启新的线程,执行run方法
java程序属于抢占式调度,那个线程的优先级高,哪个线程先执行;同一个优先级,随机选择一个执行
// 1. 创建一个Thread类的子类
public class MyThread extends Thread{
// 2. 在Thread类的子类中重写Thread的run方法,设置线程任务(开启线程要做什么?)
@Override
public void run(){
for (int i = 0; i < 20; i++) {
System.out.println("run"+i);
}
}
}
public class Demo01Thread {
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);
}
}
}
创建多线程程序的第二种方式:实现Runnable接口
实现步骤:
1. 创建一个Runnable接口的实现类
2. 在实现类中重写Runnable接口的run方法,设置线程任务
3. 创建一个Runnable接口的实现类对象
4. 创建Thread类对象,构造方法中传递Runnable接口的实现类对象
5. 调用Thread类中的start方法,开启新的线程执行run方法
// 1. 创建一个Runnable接口的实现类
public class RunnableImpl implements Runnable {
// 2. 在实现类中重写Runnable接口的run方法,设置线程任务
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + "---->" + i);
}
}
}
public class Demo02Runnable {
public static void main(String[] args) {
// 3. 创建一个Runnable接口的实现类对象
RunnableImpl run = new RunnableImpl();
// 4. 创建Thread类对象,构造方法中传递Runnable接口的实现类对象
Thread t = new Thread(run, "无情");
// 5. 调用Thread类中的start方法,开启新的线程执行run方法
t.start();
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+"---->"+i);
}
}
}
实现Runnable创建多线程程序的好处:
1.避免了单继承的局限性
一个类只能继承一个类,类继承了Thread就不能继承其它的类。
实现了Runnable接口,还可以继承其它的类,实现其它的接口
2.增强程序的扩展性,降低程度的耦合性(解耦)
实现Runnable接口的方式,把设置线程任务和开启新线程进行了分离(解耦)
实现类中,重写了run方法:用来设置线程任务
创建Thread类对象,调用start方法:开启新的线程
建议使用runnable接口实现多线程。
通过匿名内部类实现线程的创建
public class Demo03Anonymous {
public static void main(String[] args) {
Runnable r = new Runnable(){
@Override
public void run() {
for (int i = 0; i < 200; i++) {
System.out.println("张宇" + i);
}
}
};
new Thread(r).start();
for (int i = 0; i < 200; i++) {
System.out.println("费玉清" + i);
}
}
}
线程的安全
通过一个案例,讲述线程安全问题:
电影院在卖100张票,三个售票窗口同时卖;
public class TicketImpl implements Runnable{
private int ticket = 100;
@Override
public void run() {
while (true){
if (ticket>0){
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
String name = Thread.currentThread().getName();
System.out.println(name + "正在卖:" + ticket--);
}
}
}
}
public class Demo04Ticket {
public static void main(String[] args) {
// 创建线程任务对象
TicketImpl ticket = new TicketImpl();
// 创建三个窗口对象
Thread t1 = new Thread(ticket, "窗口1");
Thread t2 = new Thread(ticket, "窗口2");
Thread t3 = new Thread(ticket, "窗口3");
// 启动线程,开始卖票
t1.start();
t2.start();
t3.start();
}
}
输出:
...
窗口3正在卖:17
窗口1正在卖:17
窗口2正在卖:16
窗口1正在卖:15
窗口3正在卖:15
...
窗口1正在卖:0
窗口3正在卖:-1
此时出现:
- 相同的票被卖了多次
- 不存在的票,0和-1
这样的票数不同步问题,成为线程不安全
解锁线程安全问题有三种方法
- 同步代码块
- 同步方法
- 锁机制
同步代码块
synchronized关键字用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。格式:
synchronized(同步锁){
需要同步操作的代码
}
同步锁:
1.锁对象可以是任意类型
2.多个线程对象,要使用同一把锁
public class TicketImpl implements Runnable{
private int ticket = 100;
Object lock = new Object();
@Override
public void run() {
while (true){
synchronized (lock){
if (ticket>0){
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
String name = Thread.currentThread().getName();
System.out.println(name + "正在卖:" + ticket--);
}
}
}
}
}
同步方法
使用synchronized修饰的方法就叫同步方法,保证线程对方法的访问时互斥的。
public synchronized void method(){
可能会产生线程安全问题的代码
}
public class TicketImpl implements Runnable{
private int ticket = 100;
Object lock = new Object();
// 使用同步方法
public synchronized void sellTicket(){
if (ticket>0){
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
String name = Thread.currentThread().getName();
System.out.println(name + "正在卖:" + ticket--);
}
}
@Override
public void run() {
while (true){
sellTicket();
}
}
}
Lock锁
java.util.concurrent.locks.Lock机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作,同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象。
Lock锁也称同步锁,加锁与释放锁方法化了,如下:
- public void lock() :加同步锁。
- public void unlock() :释放同步锁。
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TicketImpl implements Runnable{
private int ticket = 100;
Lock lock = new ReentrantLock();
@Override
public void run() {
while (true){
lock.lock();
if (ticket>0){
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
String name = Thread.currentThread().getName();
System.out.println(name + "正在卖:" + ticket--);
}
lock.unlock();
}
}