实现Runnable接口比继承Thread类更有优势:
1.因为java只能单继承,实现Runnable接口可以避免单继承的局限性
2.继承Thread类,多个线程不能处理或者共享同一个资源,但是实现Runnable接口可以处理同一个资源。
public class TicketsThread extends Thread{
//用静态变量存放这100张票,这样就不会卖重复
private int tickets = 100;
public void run(){
while(true){
if(tickets > 0){
System.out.println(Thread.currentThread().getName()+"***sale***"+(--tickets));
}
}
}
/**
* @param args
*/
public static void main(String[] args) {
// 不合理的买票程序,因为,不同线程都可以卖同一张票,
//现实生活中不是这样的,窗口1买完第99张票,窗口2不可以卖了。
TicketsThread t1 = new TicketsThread();
TicketsThread t2 = new TicketsThread();
TicketsThread t3 = new TicketsThread();
t1.start();
t2.start();
t3.start();
}
}
为什么要将Runnable接口子类的对象传递给Thread类的构造方法?
因为线程的任务(要运行的代码)都封装在Runnable接口的子类对象的run方法中,所以在线程对象创建的时候就必须明确要运行的任务。
public class TicketsRunnable implements Runnable {
private int ticket=100;
//Object obj = new Object();
public TicketsRunnable(){
System.out.println("*****************************");
}
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
//synchronized(obj){ //同步代码块
if(ticket > 0){//当线程0被调起的时候,当执行到这条判断语句的时候,线程1被调起抢了CPU资源,线程0进入冻结状态。
try {
Thread.sleep(100);//中断当前活跃的线程,或者执行的线程
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在卖票"+ticket--);
//System.out.println(Thread.currentThread().getId());
//System.out.println(Thread.currentThread().getName());
}
//}
}
}
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
TicketsRunnable runna = new TicketsRunnable();
Thread t1 = new Thread(runna);
Thread t2 = new Thread(runna);
Thread t3 = new Thread(runna);
t1.start();
t2.start();
t3.start();
}
}
分析:当100张票卖到剩余最后1张的时候,也就是ticket=1的时候,有三个线程Thread-0,Thread-1,Thread-2,这时候,Thread-0进入if(ticket > 0)这个条件具备了执行资格,但不具备执行权。正在这个时候,CPU切换到了Thread-1,Thread-1也进入了if(ticket > 0)这个条件下面,CPU又切换到了Thread-2,Thread-2又进入阻塞状态。这个时候三个线程都通过了if(ticket > 0)判断,都要往下执行了,这个时候CPU的资源被Thread-0第一个线程抢到,执行ticket=1,ticket--,那么第一个线程执行完打印出ticket=1,CPU被Thread-2抢到,打印出ticket=0,同理线程3执行完打印出ticket=-1,这就出现了线程安全的问题。
通过上面的分析大家知道了多线程安全产生的原因:当多条语句(if(ticket>0)和ticket--)在操作同一个线程的共享数据的时候(这里共享数据为ticket=100),一个线程执行了多条语句的一部分,还没有执行完,另一个线程抢到CPU资源,执行。导致数据共享错误。那么怎么解决呢?
解决办法:当多条语句在操作共享数据时,只能让一个线程执行完,在执行的过程中,其它线程不可以参与执行。java对于多线程安全问题,提供了专业的解决方法,那就是锁。
synchronized(对象){
需要被同步的代码
}
public class TicketsRunnable implements Runnable {
private int ticket=100;
Object obj = new Object();//对象锁,共同步代码块使用
public TicketsRunnable(){
System.out.println("*****************************");
}
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
synchronized(obj){ //同步代码块
if(ticket > 0){//当线程0被调起的时候,当执行到这条判断语句的时候,线程1被调起抢了CPU资源,线程0进入冻结状态。
try {
Thread.sleep(100);//中断当前活跃的线程,或者执行的线程
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在卖票"+ticket--);
//System.out.println(Thread.currentThread().getId());
//System.out.println(Thread.currentThread().getName());
}
}
}
}
/**
* @param args
*/
public static void main(String[] args) {
TicketsRunnable runna = new TicketsRunnable();
Thread t1 = new Thread(runna);
Thread t2 = new Thread(runna);
Thread t3 = new Thread(runna);
t1.start();
t2.start();
t3.start();
}
}
通过测试,不会出现错票的问题。
分析代码:当火车票剩余1张的时候,这个时候假设Thread-0获取到了CPU的执行权,并且持有对象锁,进入if条件,打印出买票1。ticket--,这个时候即使其他的线程获取到CPU的执行资格,但是设Thread-0的锁还没有释放,其他的线程拿不到锁,这样就进入不了if条件,那么只要等Thread-0执行完,Thread-0执行完后,ticket=0,其他线程即使拿到锁,因为if(ticket > 0)不能进入,所以执行不了。整个程序结束,卖票终止。
总结:通过程序可以知道:同步的前提是:1.必须要有两个或者两个以上的线程,才需要同步。
2.必须是多个线程使用同一个锁。
3.要分析哪段代码需要加同步,必须保证同步中只能有一个线程在运行。
同步锁的好处与弊端: 1.好处,解决了多线程操作同意资源安全性问题。
2.弊端:多个线程每次都需要判断锁,较为消耗资源
多线程:生产者消费者模型
1.wait和notify,notifyAll:
wait和notify,notifyAll是Object类方法,因为等待和唤醒必须是同一个锁,不可以对不同锁中的线程进行唤醒,而锁可以是任意对象,所以可以被任意对象调用的方法,定义在Object基类中。
wait()方法:对此对象调用wait方法导致本线程放弃对象锁,让线程处于冻结状态,进入等待线程的线程池当中。wait是指已经进入同步锁的线程,让自己暂时让出同步锁,以便使其他正在等待此锁的线程可以进入同步锁并运行,只有其它线程调用notify方法或者notifyAll方法后,才能唤醒线程池中等待的线程。
notify()方法:唤醒线程池中的任意一个线程。
notifyAll方法:唤醒线程池中的所有线程
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class Resource {
private boolean flag = false;
private int num;
Object object = new Object();
public void product() {
synchronized (object) {//同步锁可以是this,类字节码,等 但是对应的调用锁对象的wait() notify()方法
while (flag) { //每次判断避免生产者唤醒生产者然后多次生产不消费
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + " product " + ++num);
flag = true;
object.notifyAll();//不用notify 避免只剩两个生产者时一个生产后等待唤醒另外一个生产者继续等待后出现的生产者消费者都处于等待状态
}
}
public void consume() {
synchronized (object) {
while (!flag) {
try {
synchronized (Resource.class) {
object.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + " consume " + num);
flag = false;
object.notifyAll();
}
}
}
public class Consumer implements Runnable {
private Resource resource;
public Consumer(Resource resource) {
this.resource = resource;
}
@Override
public void run() {
while (true) {
resource.consume();
}
}
}
public class Product implements Runnable {
private Resource resource;
public Product(Resource resource) {
this.resource = resource;
}
@Override
public void run() {
while (true) {
resource.product();
}
}
}
@Test
public void testProCon() {
Resource resource = new Resource();
Product product = new Product(resource);
Product product2 = new Product(resource);
Consumer consumer = new Consumer(resource);
Consumer consumer2 = new Consumer(resource);
new Thread(product).start();
new Thread(product2).start();
new Thread(consumer).start();
new Thread(consumer2).start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
实现Runnable接口比继承Thread类更有优势:
1.因为java只能单继承,实现Runnable接口可以避免单继承的局限性
2.继承Thread类,多个线程不能处理或者共享同一个资源,但是实现Runnable接口可以处理同一个资源。
public class TicketsThread extends Thread{
//用静态变量存放这100张票,这样就不会卖重复
private int tickets = 100;
public void run(){
while(true){
if(tickets > 0){
System.out.println(Thread.currentThread().getName()+"***sale***"+(--tickets));
}
}
}
/**
* @param args
*/
public static void main(String[] args) {
// 不合理的买票程序,因为,不同线程都可以卖同一张票,
//现实生活中不是这样的,窗口1买完第99张票,窗口2不可以卖了。
TicketsThread t1 = new TicketsThread();
TicketsThread t2 = new TicketsThread();
TicketsThread t3 = new TicketsThread();
t1.start();
t2.start();
t3.start();
}
}
为什么要将Runnable接口子类的对象传递给Thread类的构造方法?
因为线程的任务(要运行的代码)都封装在Runnable接口的子类对象的run方法中,所以在线程对象创建的时候就必须明确要运行的任务。
public class TicketsRunnable implements Runnable {
private int ticket=100;
//Object obj = new Object();
public TicketsRunnable(){
System.out.println("*****************************");
}
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
//synchronized(obj){ //同步代码块
if(ticket > 0){//当线程0被调起的时候,当执行到这条判断语句的时候,线程1被调起抢了CPU资源,线程0进入冻结状态。
try {
Thread.sleep(100);//中断当前活跃的线程,或者执行的线程
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在卖票"+ticket--);
//System.out.println(Thread.currentThread().getId());
//System.out.println(Thread.currentThread().getName());
}
//}
}
}
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
TicketsRunnable runna = new TicketsRunnable();
Thread t1 = new Thread(runna);
Thread t2 = new Thread(runna);
Thread t3 = new Thread(runna);
t1.start();
t2.start();
t3.start();
}
}
分析:当100张票卖到剩余最后1张的时候,也就是ticket=1的时候,有三个线程Thread-0,Thread-1,Thread-2,这时候,Thread-0进入if(ticket > 0)这个条件具备了执行资格,但不具备执行权。正在这个时候,CPU切换到了Thread-1,Thread-1也进入了if(ticket > 0)这个条件下面,CPU又切换到了Thread-2,Thread-2又进入阻塞状态。这个时候三个线程都通过了if(ticket > 0)判断,都要往下执行了,这个时候CPU的资源被Thread-0第一个线程抢到,执行ticket=1,ticket--,那么第一个线程执行完打印出ticket=1,CPU被Thread-2抢到,打印出ticket=0,同理线程3执行完打印出ticket=-1,这就出现了线程安全的问题。
通过上面的分析大家知道了多线程安全产生的原因:当多条语句(if(ticket>0)和ticket--)在操作同一个线程的共享数据的时候(这里共享数据为ticket=100),一个线程执行了多条语句的一部分,还没有执行完,另一个线程抢到CPU资源,执行。导致数据共享错误。那么怎么解决呢?
解决办法:当多条语句在操作共享数据时,只能让一个线程执行完,在执行的过程中,其它线程不可以参与执行。java对于多线程安全问题,提供了专业的解决方法,那就是锁。
synchronized(对象){
需要被同步的代码
}
public class TicketsRunnable implements Runnable {
private int ticket=100;
Object obj = new Object();//对象锁,共同步代码块使用
public TicketsRunnable(){
System.out.println("*****************************");
}
@Override
public void run() {
// TODO Auto-generated method stub
while(true){
synchronized(obj){ //同步代码块
if(ticket > 0){//当线程0被调起的时候,当执行到这条判断语句的时候,线程1被调起抢了CPU资源,线程0进入冻结状态。
try {
Thread.sleep(100);//中断当前活跃的线程,或者执行的线程
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在卖票"+ticket--);
//System.out.println(Thread.currentThread().getId());
//System.out.println(Thread.currentThread().getName());
}
}
}
}
/**
* @param args
*/
public static void main(String[] args) {
TicketsRunnable runna = new TicketsRunnable();
Thread t1 = new Thread(runna);
Thread t2 = new Thread(runna);
Thread t3 = new Thread(runna);
t1.start();
t2.start();
t3.start();
}
}
通过测试,不会出现错票的问题。
分析代码:当火车票剩余1张的时候,这个时候假设Thread-0获取到了CPU的执行权,并且持有对象锁,进入if条件,打印出买票1。ticket--,这个时候即使其他的线程获取到CPU的执行资格,但是设Thread-0的锁还没有释放,其他的线程拿不到锁,这样就进入不了if条件,那么只要等Thread-0执行完,Thread-0执行完后,ticket=0,其他线程即使拿到锁,因为if(ticket > 0)不能进入,所以执行不了。整个程序结束,卖票终止。
总结:通过程序可以知道:同步的前提是:1.必须要有两个或者两个以上的线程,才需要同步。
2.必须是多个线程使用同一个锁。
3.要分析哪段代码需要加同步,必须保证同步中只能有一个线程在运行。
同步锁的好处与弊端: 1.好处,解决了多线程操作同意资源安全性问题。
2.弊端:多个线程每次都需要判断锁,较为消耗资源
来源:https://www.cnblogs.com/leifonlyone/p/12371959.html