线程的调度:
1分时调度
所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间;
2抢占式调度
优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),java使用的为抢占式调度;
主线程:执行主方法的线程main
单线程程序有弊端,中间错误则不能向下执行;
创建多线程的第一种方式:
package demo06;
//创建一个Thread的子类
public class demo01thread extends Thread{
public void run(){
for (int i=0;i<20;i++)
{
System.out.println("run"+i);
}
}
}
package demo06;
/*创建多线程的第一种方式,创建Thread类的子类
* java.lang.Thread类,是描述线程的类,我们想要实现多线程程序就必须继承Thread类
*
* 实现步骤:
* 1创建一个Thread类的子类
* 2在Thread类的子类中重写Thread类中的run方法,设置线程任务(开启线程要做什么?)
* 3创建一个Thread类的子类对象
* 4调用Thread类中的方法start方法,开启新的线程,执行run方法
* void start()使用该线程开始执行,java虚拟机调用该线程的run方法;
* 结果是两个线程并发地执行,当前线程(main线程)和另一个线程(创建的新线程执行其run方法)
* 多次启动一个线程是非法的,特别是当线程已经结束执行后,不能再重新启动;
*java程序属于抢占式调度,那个线程优先级高就优先 相同则随机
* */
public class demo06{
public static void main(String[] args) {
demo01thread mt=new demo01thread();
mt.start();
for (int i=0;i<20;i++)
{
System.out.println("main"+i);
}
}
}
多线程内存图解
package demo06;
/*
* 获取线程的名字:
* 1使用Thread类中的方法getName()
* String getName()返回该线程的名称;
* 2可以先获取当前正在执行的线程,使用线程中的方法getName()获取线程的名称;
* static Thread currentThread()返回对当前正在执行的线程对象的引用
* */
//定义一个Thread类的子类
public class demo01thread extends Thread{
// 重写Thread中的run方法,设置线程任务
public void run(){
// 获取线程名称
// String name=getName();
// System.out.println(name);
// Thread thread = Thread.currentThread();
// System.out.println(thread);
// System.out.println(thread.getName());
System.out.println(Thread.currentThread().getName());
}
}
package demo06;
import com.sun.jdi.event.ThreadStartEvent;
/*
* 线程名称:
* 主线程:Thread-0,Thread-1,Thread-2
* */
public class demo06{
public static void main(String[] args) {
demo01thread mt=new demo01thread();
// 调用start方法开启新线程,执行run方法
mt.start();
new demo01thread().start();
new demo01thread().start();
System.out.println(Thread.currentThread().getName());
}
Thread类的sleep
static void |
sleep(long millis) 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。 |
因为是静态方法所以可以直接使用类名.方法名();
创建对线程程序的Runnable接口:
package demo06;
public class demo01thread implements Runnable{
@Override
public void run() {
for (int i=0;i<20;i++){
System.out.println(Thread.currentThread().getName()+"--->"+i);
}
}
}
package demo06;
/*
* 创建多线程的第二种方式:实现Runnable接口
* java.lang.Runnable;
* Runnable 接口应该由那些打算通过某一线程执行其实例类来实现。类必须定义一个称为 run 的无参数方法。
* java.lang.Thread类的构造方法
* Thread(Runnable target)
分配新的 Thread 对象。
Thread(Runnable target, String name)
分配新的 Thread 对象。
实现步骤:
* 1创建一个Runnable接口的实现类
* 2在实现类中重写Runnable接口的run方法,设置线程任务
* 3创建一个Runnable接口的实现类对象
* 4创建Thread类对象,构造方法中传递Runnable接口的实现类对象
* 5调用Thread类中的start方法,开启新的线程执行run方法;
*
* */
public class demo06{
public static void main(String[] args) {
// 3创建一个Runnable接口的实现类对象
demo01thread run=new demo01thread();
// 4 创建一个Thread对象
Thread t =new Thread(run);
t.start();
for (int i=0;i<20;i++){
System.out.println(Thread.currentThread().getName()+"--->"+i);
}
}
}
好处:
1实现了Runnable接口,还可以继承其他类,实现其他接口;
2增强了程序的扩展性,降低了程序的耦合性(解耦)
实现Runnable接口的方式,把设置线程任务和开启新线程进行了分离(解耦)
实现类中,重写了run方法,用来设置线程任务;
创建Thread类对象,调用start方法,用来开启新的线程;
(使用接口可以较快的更改多线程的任务)
package demo06;
public class demo01thread implements Runnable{
@Override
public void run() {
for (int i=0;i<20;i++){
System.out.println(Thread.currentThread().getName()+"--->"+i);
}
}
}
package demo06;
/*
* 创建多线程的第二种方式:实现Runnable接口
* java.lang.Runnable;
* Runnable 接口应该由那些打算通过某一线程执行其实例类来实现。类必须定义一个称为 run 的无参数方法。
* java.lang.Thread类的构造方法
* Thread(Runnable target)
分配新的 Thread 对象。
Thread(Runnable target, String name)
分配新的 Thread 对象。
实现步骤:
* 1创建一个Runnable接口的实现类
* 2在实现类中重写Runnable接口的run方法,设置线程任务
* 3创建一个Runnable接口的实现类对象
* 4创建Thread类对象,构造方法中传递Runnable接口的实现类对象
* 5调用Thread类中的start方法,开启新的线程执行run方法;
*
* */
public class demo06{
public static void main(String[] args) {
// 3创建一个Runnable接口的实现类对象
demo01thread run=new demo01thread();
// 4 创建一个Thread对象,将run方法传递到该新建的线程
Thread t =new Thread(run);//打印线程名称
t.start();
for (int i=0;i<20;i++){
System.out.println(Thread.currentThread().getName()+"--->"+i);
}
}
}
匿名内部类方式实现线程的创建
package demo06;
public class demo01thread implements Runnable{
@Override
public void run() {
for (int i=0;i<20;i++){
System.out.println(Thread.currentThread().getName()+"--->"+i);
}
}
}
package demo06;
/*
* 匿名内部类方式实现线程的创建
* 匿名:没有名字
* 内部类:写在其他类内部的类
*
* 匿名内部类作用:简化代码
* 把子类继承父类,重写父类的方法,创建子类对象合成一步完成
* 把实现类实现类接口,重写接口中的方法,创建实现类对象合成一步完成;
* 匿名内部类的最终产物:子类/实现类对象,而这个类没有名字;
* 格式:new 父类/接口(){
* 重复父类/接口中的方法
* };
* */
public class demo06{
public static void main(String[] args) {
// 线程的父类是Thread
// new MyThread().start();
new Thread(){
// 重写run方法,设置线程任务
public void run(){
for (int i=0;i<20;i++){
System.out.println(Thread.currentThread().getName()+"--->"+"黑马");
}
}
}.start();
// 线程的接口Runnable
// Runnable r=new demo01thread();多态 左父右子
Runnable r = new Runnable(){
//重写run方法,设置线程任务
@Override
public void run() {
for (int i=0;i<20;i++){
System.out.println(Thread.currentThread().getName()+"--->"+"程序员");
}
}
};
new Thread(r).start();
// 简化接口的方式
new Thread(new Runnable(){
//重写run方法,设置线程任务
@Override
public void run() {
for (int i=0;i<20;i++){
System.out.println(Thread.currentThread().getName()+"--->"+"博客");
}
}
}).start();
}
}
当多线程访问了共享的数据会产生安全问题
开启多个线程之后,一起抢夺cpu 的执行权,谁抢到谁执行;
当抢到CPU执行权则进入run方法执行。
注意:线程安全问题是不能产生的,我们可以让一个线程在访问共享数据的时候,无论是否丢失了cpu的执行权,让其他的线程只能等待,等待当前线程卖完,其他线程再进行卖票
解决线程安全问题:线程同步技术(1同步代码块,2同步方法3锁机制)
synchronized
1使用同步代码块
package demo06;
/*
* 卖票案例出现了线程安全问题
* 卖出了不存在的票和重复的票
*
* 解决线程安全问题的一种方法:使用同步代码块
* 格式:
* synchronized(锁对象){
* 可能会出现线程安全问题的代码(访问了共享数据的代码)
* }
* 注意:
* 1同步代码块中的锁对象,可以使用任意的对象
* 2但是必须保证多个线程使用的锁对象是同一个
* 3锁对象的作用:
* 把同步代码块锁住,只让一个线程在同步代码块中执行
* */
public class demo01thread implements Runnable{
private int ticket=100;
// 创建一个锁对象
Object obj=new Object();
/*设置线程任务:卖票*/
@Override
public void run() {
// 让卖票重复执行
while (true){
// 创建一个同步代码块
synchronized (obj){
// 先判断是否免票
if (ticket>0){
// 提高安全问题出现的概率,让程序睡眠 此时出现线程安全问题出现了不存在的票
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 票存在,卖票
System.out.println(Thread.currentThread().getName()+"----->正在卖第"+ticket+"张票");
ticket--;
}
}
}
}
}
}
package demo06;
/*
* 模拟卖票案例
* 创建3个线程,同时开启对共享的票进行出售
* */
public class demo06{
public static void main(String[] args) {
// 创建Runnable接口的实现类对象
demo01thread run=new demo01thread();
// 创建Thread类对象,构造方法中传递Runnable接口的实现类对象
Thread t0=new Thread(run);
Thread t1=new Thread(run);
Thread t2=new Thread(run);
t0.start();
t1.start();
t2.start();
}
}
同步技术原理:
使用了一个锁对象,这个锁对象叫同步锁,也叫对象锁,也叫对象锁监视器,3个线程一起抢夺Cpu的执行权,谁被抢到了谁执行run方法进行卖票
to抢到了CPU的执行权,执行run方法,遇到synchronized代码块,这时to会检查synchronized代码块是否有锁对象,发现有,就会获取到锁对象
进入到同步执行
t1抢到了CPU的执行权,执行run方法,刅synchronized代码块,这时t1对检查synchronized代码块是否有锁对象,发现没有,t1就会进入
阻塞状态。就会一直等待t0归还对象。
总结:同步中的线程,没有执行完毕不会释放锁,同步外的线程没有锁进不去同步
2同步方法:
package demo06;
/*
* 同步方法问题
* 使用步骤:
* 1把访问了共享数据的代码抽取出来,放到一个方法中
* 2在方法上添加synchronized修饰符
* 格式:
* 修饰符 synchronized 返回值类型 方法名 (参数列表){
* 可能会出现的线程安全问题的代码(访问了共享数据的代码)
* }
* */
public class demo01thread implements Runnable{
private int ticket=100;
// 创建一个锁对象
Object obj=new Object();
/*设置线程任务:卖票*/
@Override
public void run() {
System.out.println("this"+this);
// 让卖票重复执行
while (true){
payticket();
}
}
/*
*定义一个方法
* 同步方法也是把方法内部的代码锁住,只让一个现成执行,同步方法的锁对象是谁?
* 就是实现类对象 new demo01thread() 也就是this
* */
public synchronized void payticket(){
// 先判断是否免票
if (ticket>0){
// 提高安全问题出现的概率,让程序睡眠 此时出现线程安全问题出现了不存在的票
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 票存在,卖票
System.out.println(Thread.currentThread().getName()+"----->正在卖第"+ticket+"张票");
ticket--;
}
}
}
package demo06;
/*
* 模拟卖票案例
* 创建3个线程,同时开启对共享的票进行出售
* */
public class demo06{
public static void main(String[] args) {
// 创建Runnable接口的实现类对象
demo01thread run=new demo01thread();
System.out.println("run"+run);
// 创建Thread类对象,构造方法中传递Runnable接口的实现类对象
Thread t0=new Thread(run);
Thread t1=new Thread(run);
Thread t2=new Thread(run);
// 调用start方法之前开启多线程
t0.start();
t1.start();
t2.start();
}
}
静态方法
package demo06;
/*
* 同步方法问题
* 使用步骤:
* 1把访问了共享数据的代码抽取出来,放到一个方法中
* 2在方法上添加synchronized修饰符
* 格式:
* 修饰符 synchronized 返回值类型 方法名 (参数列表){
* 可能会出现的线程安全问题的代码(访问了共享数据的代码)
* }
* */
public class demo01thread implements Runnable{
private static int ticket=100;
// 创建一个锁对象
Object obj=new Object();
/*设置线程任务:卖票*/
@Override
public void run() {
System.out.println("this"+this);
// 让卖票重复执行
while (true){
payticket();
}
}
/*
* 静态的同步方法
* 锁对象是谁?
* 不能是this this是创建对象之后产生的,静态方法优先于对象;
* 静态方法的锁对象是本类的class属性---》class文件对象(放射)
*
* */
public static synchronized void payticket(){
// 先判断是否免票
if (ticket>0){
// 提高安全问题出现的概率,让程序睡眠 此时出现线程安全问题出现了不存在的票
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 票存在,卖票
System.out.println(Thread.currentThread().getName()+"----->正在卖第"+ticket+"张票");
ticket--;
}
}
}
package demo06;
/*
* 模拟卖票案例
* 创建3个线程,同时开启对共享的票进行出售
* */
public class demo06{
public static void main(String[] args) {
// 创建Runnable接口的实现类对象
demo01thread run=new demo01thread();
System.out.println("run"+run);
// 创建Thread类对象,构造方法中传递Runnable接口的实现类对象
Thread t0=new Thread(run);
Thread t1=new Thread(run);
Thread t2=new Thread(run);
// 调用start方法之前开启多线程
t0.start();
t1.start();
t2.start();
}
}
或者换成下面的
package demo06;
/*
* 同步方法问题
* 使用步骤:
* 1把访问了共享数据的代码抽取出来,放到一个方法中
* 2在方法上添加synchronized修饰符
* 格式:
* 修饰符 synchronized 返回值类型 方法名 (参数列表){
* 可能会出现的线程安全问题的代码(访问了共享数据的代码)
* }
* */
public class demo01thread implements Runnable{
private static int ticket=100;
// 创建一个锁对象
Object obj=new Object();
/*设置线程任务:卖票*/
@Override
public void run() {
System.out.println("this"+this);
// 让卖票重复执行
while (true){
payticket();
}
}
/*
* 静态的同步方法
* 锁对象是谁?
* 不能是this this是创建对象之后产生的,静态方法优先于对象;
* 静态方法的锁对象是本类的class属性---》class文件对象(放射)
*
* */
public static /*synchronized*/ void payticket(){
synchronized (demo01thread.class){
// 先判断是否免票
if (ticket>0){
// 提高安全问题出现的概率,让程序睡眠 此时出现线程安全问题出现了不存在的票
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 票存在,卖票
System.out.println(Thread.currentThread().getName()+"----->正在卖第"+ticket+"张票");
ticket--;
}
}
}
}
package demo06;
/*
* 模拟卖票案例
* 创建3个线程,同时开启对共享的票进行出售
* */
public class demo06{
public static void main(String[] args) {
// 创建Runnable接口的实现类对象
demo01thread run=new demo01thread();
System.out.println("run"+run);
// 创建Thread类对象,构造方法中传递Runnable接口的实现类对象
Thread t0=new Thread(run);
Thread t1=new Thread(run);
Thread t2=new Thread(run);
// 调用start方法之前开启多线程
t0.start();
t1.start();
t2.start();
}
}
第三种方式:Lock锁
package demo06;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/*
* 第三种:使用Lock锁
* java.util.concurrent.locks
* Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作
* Lock提供了两种方法
* void lock()获取锁
* void unlock()释放锁
* java.util.concurrent.locks.RenntrantLock implements Lock 接口
*
* 使用步骤:
* 1在成员位置创建一个RenntrankLock对象
* 2在可能会出现安全问题的代码前调用Lock接口中的方法获取Lock获取锁
* 3在可能会出现安全问题的代码后调用Lock接口中的方法unLock释放锁
* */
public class demo01thread implements Runnable{
private static int ticket=100;
// 1在成员位置创建一个RenntrankLock对象
Lock l=new ReentrantLock(); //使用多态
/*设置线程任务:卖票*/
@Override
public void run() {
// 让卖票重复执行
while (true){
l.lock();
try {
// 先判断是否免票
if (ticket>0){
// 提高安全问题出现的概率,让程序睡眠 此时出现线程安全问题出现了不存在的票
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 票存在,卖票
System.out.println(Thread.currentThread().getName()+"----->正在卖第"+ticket+"张票");
ticket--;
}
}finally {
l.unlock();
}
}
}
public static /*synchronized*/ void payticket(){
}
}
package demo06;
/*
* 模拟卖票案例
* 创建3个线程,同时开启对共享的票进行出售
* */
public class demo06{
public static void main(String[] args) {
// 创建Runnable接口的实现类对象
demo01thread run=new demo01thread();
// 创建Thread类对象,构造方法中传递Runnable接口的实现类对象
Thread t0=new Thread(run);
Thread t1=new Thread(run);
Thread t2=new Thread(run);
// 调用start方法之前开启多线程
t0.start();
t1.start();
t2.start();
}
}