什么是多线程
线程是依赖于进程而存在的。
A:进程 正在运行的应用程序
B:线程 进程的执行路径,执行单元
多线程的创建方式
- 通过继承Thread类
- 通过实现Runnable接口
实现方式一代码如下
package com.wy.threadcreate1;
/*
* 创建线程的第一种方式,继承Thread类
*/
public class MyThread extends Thread{
//重写run方法,重写run方法中的代码之后
//当启动了这个线程之后,这个线程就会执行run方法中的代码
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(i);
}
}
}
测试类如下:
package com.wy.threadcreate1;
public class ThreadTest {
public static void main(String[] args) {
MyThread mt = new MyThread();
mt.start();
}
}
实现方式2代码如下:
package com.wy.threadcreate2;
/*
* 创建线程的第二种方式,实现Runable接口
*/
public class MyThread implements Runnable{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(i);
}
}
}
多线程方法介绍
启动线程的方法
run()
start()和run()的区别
start():1.开启线程 2.执行run()方法里面的代码 run():执行的是线程里面执行的代码,并不会开启线程
为什么要重写run()方法?
因为每个线程需要执行的代码都是都是不一样的, 我们需要将每个线程自己独立执行的代码写到run()方法中执行
- 线程可以多次启动么?
不可以多次启动
package com.wy.start_run;
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(i);
}
}
}
///////////////////////////////////////////////////////////////////////////
package com.wy.start_run;
public class ThreadTest {
public static void main(String[] args) {
MyThread mt = new MyThread();
MyThread mt2 = new MyThread();
//mt.start();//1.首先开启了一个独立的线程
//2.让这个独立的线程执行run方法中的代码
mt.start();
mt2.start();
//注意:同一个线程不能多次启动,在运行期会报错
//java.lang.IllegalThreadStateException
mt.start();
//1.普通的创对象,调方法 2.代码实在主线程执行,不会开辟新线程
mt.run();
mt.run();
}
}
线程的名称设置与优先级设置
线程休眠(Thread.sleep(毫秒值)) 线程名称(setName(),getName();) 线程的调度及优先级setPriority(10)(注意默认值是5,区间在1-10之间) 什么叫线程优先级:说白了就是设置你抢占cpu执行权抢占到的概率
package com.wy.sleep;
public class MyThread extends Thread{
@Override
public void run() {
//让线程睡眠秒
try {
MyThread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
for (int i = 0; i < 20; i++) {
System.out.println(this.getName()+i);
}
}
}
/////////////////////////////////////////////////////////////////////
package com.wy.sleep;
public class ThreadTest {
public static void main(String[] args) {
//创建三个线程对象
MyThread mt1 = new MyThread();
MyThread mt2 = new MyThread();
MyThread mt3 = new MyThread();
//给三个线程对象设置名字
mt1.setName("Thread one");
mt2.setName("Thread two");
mt3.setName("thread three");
//设置线程的优先级
//线程的调度及优先级setPriority(10)(注意默认值是5,区间在1-10之间)
mt1.setPriority(10);
mt2.setPriority(2);
//如果设置的值不在1-10之间,则会报java.lang.IllegalArgumentException
//mt3.setPriority(100);
mt1.start();
mt2.start();
mt3.start();
}
}
如果实现的是Runnable接口,则通过Thread.currentThread().getName()来获取正在运行的线程名称。
多线程安全问题
- 继承Thread卖票,会出现问题
package com.wy.multithread;
public class MyThread extends Thread{
//模拟卖票
//如果不定义成静态的,则每一个进程都会从50到1进行
//int ticket = 50;
//当设置为静态的时候,结果是会出现0,这是因为线程读取了ticket,然后进行了休眠,下一个线程也读取了此数据
static int ticket = 100;
@Override
public void run() {
while (true) {
//只有当ticket>0的时候,才可以出售票
if (ticket>0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(this.getName()+"正在出售第:"+ticket--+"张票");
}
}
}
}
///////////////////////////////////////////////////////////////////////////
package com.wy.multithread;
public class MultiTest {
public static void main(String[] args) {
MyThread mt1 = new MyThread();
MyThread mt2 = new MyThread();
MyThread mt3 = new MyThread();
mt1.setName("thread one");
mt2.setName("thread two");
mt3.setName("thread three");
mt1.start();
mt2.start();
mt3.start();
}
}
2.实现Runnable卖票(睡一会出现线程安全问题)
问题:
按照真实的情景加入了延迟,发现出现了这样的两个问题:
A:相同的票卖了多次
CPU的一次操作必须是原子性的(操作是CPU执行一次就可以直接完成的)
B:出现了负数的票
随机性和延迟导致的
出现上面的问题称为线程安全问题。
3.什么是多线程安全问题?
A:是否是多线程环境
B:是否有共享数据
C:是否有多条语句操作共享数据
4.解决多线程安全方法
*同步代码块中的问题*
如果是同一把锁,则不会出现安全问题;
如果不是同一把锁,则有安全问题。
锁对象问题
a:同步代码块(定义一个抽象类,里面专门定义一个锁)
任意对象
b:同步方法(仅适用于实现runable接口)
public synchronized void sellTicket(){同步代码}
this
c:静态同步方法
类的字节码对象
public static synchronized void sellTicket() {
需要同步的代码
}
package com.wy.multithread_3;
/*
* 解决多线程安全问题,有三种方式:
* 1.同步代码快,其对应的锁是任意一个类
* 2.同步方法,其对应的锁是this,(仅适用于实现runable接口)
* 3.静态同步方法,其对应的锁是类字节码对象
*/
public class MyThread implements Runnable{
// 定义50张票
static int ticket = 50;
Object obj = new Object();
int x = 0;
@Override
public void run() {
while (true) {
if (x % 2 == 0) {
//1.这是同步代码块对象的锁
synchronized (obj) {// 这样3个线程才可以共享同一把锁
//2.这个是同步方法对应的锁,当选择同步方法的时候,选择这个锁
//synchronized(this){
//3.这个是静态代码块对应的锁,当选择静态同步方法的时候,选择这个锁
//synchronized(MyThread.class){
if (ticket > 0) {
// 考虑到实际的生活中,我们需要给每一个线程加入一定的延迟,模拟一下这种效果
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "正在出售第:" + ticket-- + "张票");
}
}
} else {
sellTicket();
}
x++;
}
}
//1.这里是把同步代码块写到一个方法中
private void sellTicket() {
synchronized (obj) {// 这样3个线程才可以共享同一把锁
if (ticket > 0) {
// 考虑到实际的生活中,我们需要给每一个线程加入一定的延迟,模拟一下这种效果
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第:"
+ ticket-- + "张票");
}
}
}
//2.同步方法:同步方法是将synchronized关键字加到方法上,同步方法的锁是this
/*
private synchronized void sellTicket() {
if (ticket>0) {
//考虑到实际的生活中,我们需要给每一个线程加入一定的延迟,模拟一下这种效果
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在出售第:"+ticket--+"张票");
}
}*/
// 3.静态同步方法,他的锁是本类的字节码文件对象:类名.class。
/*
private static synchronized void sellTicket() {
if (ticket > 0) {
// 考虑到实际的生活中,我们需要给每一个线程加入一定的延迟,模拟一下这种效果
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第:"
+ ticket-- + "张票");
}
}
*/
}
5.匿名内部类的实现
两种方式
第一种方式:
new Thread() {
public void run() {
...
}
}.start();
第二种方式:
new Thread(new Runnable(){
public void run() {
...
}
}).start();
package com.wy.anonymity;
public class Anonymity {
/**
* 匿名内部类的方式使用多线程
* new Thread() { public void run() { ... } }.start();
*
* new Thread(new Runnable(){ public void run() { ... } }).start();
*
*/
public static void main(String[] args) {
// 方式1:
new Thread() {
// 重写的方法
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
}
}.start();
// 方式2
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
}
}).start();
}
}
6.JDK5的Lock锁
JDK5的Lock锁,我们之前造的所有的锁都没有手动释放锁
static Lock lock = new ReentrantLock();
枷锁:lock.lock();
释放锁:lock.unlock();
可以让我们明确的知道在哪里加锁和释放锁。
7.死锁问题
同步嵌套,锁里面套了一个锁,出现同步嵌套。
8.线程的等待和唤醒
锁对象调用wait()锁对象调用notify()
wait和sleep的区别
线程等待,在等待的同时释放锁,而sleep()方法在执行的过程中是不会释放锁的