一、创建多线程
第二种方式:实现 Runnable 接口
步骤:
1,定义类实现Runnable接口;
2,覆盖Runnable接口中的 run 方法;
目的:将线程要运行的代码存放在该run方法中。
3,通过Thread 类建立线程对象;
4,将Runnable 接口的子类对象作为实际参数传递给Thread类的构造函数
5,调用Thread类的start 方法开启线程并调用Runnable接口子类的run方法。
二、创建多线程两种方式(继承Thread 和实现Runnable)的区别:
两种方式的区别:(线程代码存放位置不同)
继承Thread类:线程代码存放在Thread子类run方法中;
实现Runnable:线程代码存放在接口的子类的run方法中;
Java只支持单继承(即一个类只能有一个父类),但支持多实现。如果一个类本身已经继承了一个父类,如果想通过继承Thread类来启动多线程,将不可行。因此此刻已经超出继承父类的个数了。此时可以通过实现Runnable接口,来启动多线程。
实现方式的优点:避免单继承的局限性
在定义线程的时候,推荐使用实现的方式创建线程
public class TicketThread {
public static void main(String[] args) {
// TODO Auto-generated method stub
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
t1.start();
t2.start();
t3.start();
}
}
class Ticket implements Runnable{
private static int ticket = 50;
public void run() {
while (true) {
if (ticket==0) {
break;
}
if (ticket>0) {
System.out.println(Thread.currentThread().getName()+ " sale: "+ ticket--);
}
}
}
}
执行结果:
Thread-0 sale: 50
Thread-1 sale: 48
Thread-2 sale: 49
Thread-1 sale: 46
Thread-1 sale: 44
Thread-0 sale: 47
Thread-0 sale: 42
Thread-0 sale: 41
Thread-0 sale: 40
Thread-0 sale: 39
Thread-0 sale: 38
Thread-0 sale: 37
Thread-0 sale: 36
Thread-0 sale: 35
Thread-0 sale: 34
Thread-0 sale: 33
Thread-0 sale: 32
Thread-0 sale: 31
Thread-0 sale: 30
Thread-0 sale: 29
Thread-0 sale: 28
Thread-1 sale: 43
Thread-2 sale: 45
Thread-2 sale: 25
Thread-2 sale: 24
Thread-2 sale: 23
Thread-2 sale: 22
Thread-2 sale: 21
Thread-1 sale: 26
Thread-0 sale: 27
Thread-0 sale: 18
Thread-0 sale: 17
Thread-0 sale: 16
Thread-0 sale: 15
Thread-1 sale: 19
Thread-2 sale: 20
Thread-2 sale: 12
Thread-2 sale: 11
Thread-2 sale: 10
Thread-2 sale: 9
Thread-2 sale: 8
Thread-2 sale: 7
Thread-2 sale: 6
Thread-2 sale: 5
Thread-2 sale: 4
Thread-2 sale: 3
Thread-2 sale: 2
Thread-1 sale: 13
Thread-0 sale: 14
Thread-2 sale: 1
三、多线程同步代码块
问题描述:50张票,每执行一次run方法,票数减一,直至ticket 为0;代码描述情景:
public class TicketThread {
public static void main(String[] args) {
// TODO Auto-generated method stub
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
t1.start();
t2.start();
t3.start();
}
}
class Ticket implements Runnable{
private static int ticket = 50;
public void run() {
while (true) {
if (ticket>0) {
//此处用异常模拟系统切换到其他线程中执行操作
try {Thread.sleep(10);} catch (Exception e) {}
System.out.println(Thread.currentThread().getName()+ " sale: "+ ticket--);
}
}
}
}
执行结果如下:
Thread-2 sale: 50
Thread-1 sale: 49
Thread-0 sale: 48
Thread-1 sale: 47
Thread-2 sale: 46
Thread-2 sale: 45
Thread-1 sale: 44
Thread-0 sale: 43
Thread-1 sale: 42
Thread-2 sale: 41
Thread-1 sale: 40
Thread-2 sale: 39
Thread-0 sale: 38
Thread-2 sale: 37
Thread-1 sale: 36
Thread-0 sale: 35
Thread-1 sale: 34
Thread-2 sale: 33
Thread-1 sale: 32
Thread-2 sale: 31
Thread-0 sale: 30
Thread-2 sale: 29
Thread-1 sale: 28
Thread-0 sale: 27
Thread-1 sale: 26
Thread-2 sale: 25
Thread-0 sale: 24
Thread-2 sale: 23
Thread-1 sale: 22
Thread-1 sale: 21
Thread-2 sale: 20
Thread-1 sale: 19
Thread-2 sale: 18
Thread-0 sale: 17
Thread-2 sale: 16
Thread-1 sale: 15
Thread-1 sale: 14
Thread-2 sale: 13
Thread-0 sale: 12
Thread-2 sale: 11
Thread-1 sale: 10
Thread-0 sale: 9
Thread-1 sale: 8
Thread-2 sale: 7
Thread-2 sale: 6
Thread-1 sale: 5
Thread-0 sale: 4
Thread-1 sale: 3
Thread-2 sale: 2
Thread-0 sale: 1
Thread-1 sale: 0
Thread-2 sale: -1
出现了ticket为负数的情况,但是代码中循环只有ticket大于0才执行减一的操作,因此线程出现不安全的情况。
问题原因:
当多条语句操作同一个线程的共享数据时,一个线程对多条语句只执行了一部分操作,而且还没执行完,另一个线程参与进
来,导致共享数据的错误;
同步代码块的格式:
synchronized(对象){
// 需要被同步的代码块
}
场景类似:排队使用独立卫生间
同步的前提:
1,必须要有两个或者两个以上的线程。
2,必须是多个线程使用同一个锁
对象如同锁。持有锁的线程可以在同步中执行。没有持有锁的线程即使获取cpu的执行权,也进不去,因为没有获取锁。
此时可以通过加入同步代码块解决问题,代码如下:
package Thread;
public class TicketThread {
public static void main(String[] args) {
// TODO Auto-generated method stub
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
t1.start();
t2.start();
t3.start();
}
}
class Ticket implements Runnable{
private static int ticket = 50;
Object obj = new Object();
public void run() {
while (true) {
synchronized (obj) {
if (ticket>0) {
try {Thread.sleep(10);} catch (Exception e) {}
System.out.println(Thread.currentThread().getName()+ " sale: "+ ticket--);
}
}
}
}
}
执行结果
Thread-2 sale: 50
Thread-2 sale: 49
Thread-2 sale: 48
Thread-2 sale: 47
Thread-2 sale: 46
Thread-2 sale: 45
Thread-2 sale: 44
Thread-2 sale: 43
Thread-2 sale: 42
Thread-2 sale: 41
Thread-2 sale: 40
Thread-2 sale: 39
Thread-2 sale: 38
Thread-2 sale: 37
Thread-2 sale: 36
Thread-2 sale: 35
Thread-2 sale: 34
Thread-2 sale: 33
Thread-2 sale: 32
Thread-2 sale: 31
Thread-2 sale: 30
Thread-2 sale: 29
Thread-2 sale: 28
Thread-2 sale: 27
Thread-2 sale: 26
Thread-2 sale: 25
Thread-2 sale: 24
Thread-2 sale: 23
Thread-2 sale: 22
Thread-2 sale: 21
Thread-2 sale: 20
Thread-2 sale: 19
Thread-2 sale: 18
Thread-2 sale: 17
Thread-2 sale: 16
Thread-2 sale: 15
Thread-2 sale: 14
Thread-2 sale: 13
Thread-2 sale: 12
Thread-2 sale: 11
Thread-2 sale: 10
Thread-2 sale: 9
Thread-2 sale: 8
Thread-2 sale: 7
Thread-2 sale: 6
Thread-2 sale: 5
Thread-2 sale: 4
Thread-2 sale: 3
Thread-2 sale: 2
Thread-2 sale: 1
同步代码块优缺点
优点:解决多线程安全问题;
缺点:多个线程需要判断锁,较为消耗资源;
多线程找安全问题的方法:
1,明确哪些代码是多线程运行代码;
2,明确共享数据;
3,明确多线程运行代码中哪些语句是操作共享数据的;
四、同步函数
将 synchronized 以关键字的形式放在函数定义上,该函数就是同步函数,此时锁是“this”
例如:
public class TicketThread {
public static void main(String[] args) {
// TODO Auto-generated method stub
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
t1.start();
t2.start();
t3.start();
}
}
class Ticket implements Runnable{
private static int ticket = 1000;
public void run() {
while (true) {
show();
}
}
//同步函数
public synchronized void show() {
if (ticket>0) {
try {Thread.sleep(10);} catch (Exception e) {}
System.out.println(Thread.currentThread().getName()+ " sale: "+ ticket--);
}
}
}
静态的同步方法,使用的锁是该方法所在类的字节码文件对象。 类名.class 该对象的类型是class
//懒汉式的作用:延迟加载;
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;
}
}