用java创建线程的实时好处是什么?

yduiuuwa  于 2021-06-27  发布在  Java
关注(0)|答案(2)|浏览(201)
class ThreadDemo extends Thread
{  
    public void run()
    {   
        for(int i =0; i<5;i++)
        {
            System.out.println(i);
        }
    }  

}
class ThreadApp
{
    public static void main(String args[])
    {  
        ThreadDemo thread1 =  new ThreadDemo();  
        thread1.start();  
        ThreadDemo thread2 =  new ThreadDemo();  
        thread2.start();  
        ThreadDemo thread3 =  new ThreadDemo();  
        thread3.start();  
    }  
}

输出:

0
2
3
1
4

1
2
4
3
0

0
1
2
3
4

默认情况下,java应用程序是单线程应用程序。我们将使用称为多线程的概念来共享工作。意思是说,如果我们创建线程,那么它将简化工作,而不是使用一个线程(主线程)来完成工作。我从理论上理解这件事。当我开始编写代码时,我的疑问就出现了。在上面的程序中,我创建了3个线程。如果3个线程在同一逻辑上工作(迭代并使用for循环打印值),为什么它要给出3个单独的输出,而不是给出一组0到4的值?

sxpgvts3

sxpgvts31#

试着做一些不同的测试,看看自己是从哪里来的

public class ThreadApp {
    public static void main(String args[]) throws InterruptedException {
        ThreadDemo thread1 = new ThreadApp().new ThreadDemo("t1",4);
        ThreadDemo thread2 = new ThreadApp().new ThreadDemo("t2",7);
        thread2.start();
        thread1.start();

        ThreadDemo thread3 = new ThreadApp().new ThreadDemo("t3",2);
        // wait till t1 &t2 finish run then launch t3
        thread1.join();
        thread2.join();
        thread3.start();
    }

    class ThreadDemo extends Thread {
        int stop;
        public ThreadDemo(String name, int stop) {
            super(name);
            this.stop = stop;
        }

        public void run() {
            for (int i = 0; i < stop; i++) {
                System.out.println(this.getName() + ":" + i);
            }
        }

    }
}

可能输出:

t2:0
t2:1
t1:0
t2:2
t1:1
t2:3
t1:2
t2:4
t1:3
t2:5
t2:6
//due to join t3 start only after t1 & t2 finish their run
t3:0
t3:1

关于福利,只有一个提示 Producer-Consumer 问题。。。

m4pnthwp

m4pnthwp2#

复制工作,而不是分享工作

你说:
我们将使用称为多线程的概念来共享工作。
但你没有分享这份工作。你重复了这项工作。而不是运行 for 循环一次,你跑了 for 循环三次,每三个线程一次。
你问:
为什么它给出3个独立的输出,而不是给出一组0到4的值?
如果一个学校老师让三个学生在黑板上写字母表,我们最终得到的不是26个字母,而是78个字母(3*26)。每个学生都会在字母表的字母间循环。同样地,三个线程中的每一个都通过0到4的计数循环。
你的 for 循环在任务代码中是本地的。所以每个线程都从顶部开始运行所有代码。所以 for 循环执行三次,每个线程一次。

注意:system.out打印不正常

发送文本到 System.out 通过呼叫 println 或者 print 不会导致文本立即按发送顺序出现。您发送的文本行可能会出现错误。
在检查语句序列时,始终包含一个时间戳,例如 java.time.Instant.now() . 然后研究输出。您可能需要使用文本编辑器手动重新排序输出以查看真实序列。
您可以在下面我自己的示例输出中看到不按时间顺序排列的行。

执行人服务

在现代java中,我们不再需要解决 Thread 直接上课。通常最好使用executors框架。请参见oracle教程。

package work.basil.example;

import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class Counter
{
    public static void main ( String[] args )
    {
        ExecutorService executorService = Executors.newCachedThreadPool();
        Runnable task = new Runnable()
        {
            @Override
            public void run ( )
            {
                for ( int i = 0 ; i < 5 ; i++ )
                {
                    System.out.println( "i = " + i + " at " + Instant.now() + " on thread " + Thread.currentThread().getName() );
                }
            }
        };
        executorService.submit( task );
        executorService.submit( task );
        executorService.submit( task );

        // Let our program run a while, then gracefully shutdown the executor service.
        // Otherwise the backing thread pool may run indefinitely, like a zombie ?‍.
        try { Thread.sleep( Duration.ofSeconds( 5 ).toMillis() ); }catch ( InterruptedException e ) { e.printStackTrace(); }
        executorService.shutdown();
        try { executorService.awaitTermination( 1 , TimeUnit.MINUTES ); } catch ( InterruptedException e ) { e.printStackTrace(); }
    }
}

当你跑的时候。

i = 0 at 2021-01-06T05:28:34.349290Z on thread pool-1-thread-1
i = 1 at 2021-01-06T05:28:34.391997Z on thread pool-1-thread-1
i = 0 at 2021-01-06T05:28:34.349246Z on thread pool-1-thread-2
i = 0 at 2021-01-06T05:28:34.349464Z on thread pool-1-thread-3
i = 1 at 2021-01-06T05:28:34.392467Z on thread pool-1-thread-3
i = 2 at 2021-01-06T05:28:34.392162Z on thread pool-1-thread-1
i = 2 at 2021-01-06T05:28:34.392578Z on thread pool-1-thread-3
i = 3 at 2021-01-06T05:28:34.392670Z on thread pool-1-thread-1
i = 3 at 2021-01-06T05:28:34.392773Z on thread pool-1-thread-3
i = 4 at 2021-01-06T05:28:34.393165Z on thread pool-1-thread-3
i = 1 at 2021-01-06T05:28:34.392734Z on thread pool-1-thread-2
i = 4 at 2021-01-06T05:28:34.392971Z on thread pool-1-thread-1
i = 2 at 2021-01-06T05:28:34.395138Z on thread pool-1-thread-2
i = 3 at 2021-01-06T05:28:34.396407Z on thread pool-1-thread-2
i = 4 at 2021-01-06T05:28:34.397002Z on thread pool-1-thread-2

织布机项目

projectloom有望为java带来一些新特性,比如虚拟线程(纤维)和 ExecutorServiceAutoCloseable 与资源一起使用时,请尝试自动关闭。
让我们重写上面的代码以使用projectloom技术。基于早期访问java16的初步构建现在可用。
此外,我们可以用更简单的lambda语法重写上面看到的匿名类。
与上面的另一个区别是:虚拟线程没有名称。因此,我们切换到使用线程的id号来区分正在运行的线程。

try
        (
                ExecutorService executorService = Executors.newVirtualThreadExecutor() ;
        )
{
    Runnable task = ( ) -> {
        for ( int i = 0 ; i < 5 ; i++ )
        {
            System.out.println( "i = " + i + " at " + Instant.now() + " on thread " + Thread.currentThread().getId() );
        }
    };
    executorService.submit( task );
    executorService.submit( task );
    executorService.submit( task );
}
// At this point, the flow-of-control blocks until all submitted tasks are done.
// And the executor service is also shutdown by this point.

当你跑的时候。

i = 0 at 2021-01-06T05:41:36.628800Z on thread 17
i = 1 at 2021-01-06T05:41:36.647428Z on thread 17
i = 2 at 2021-01-06T05:41:36.647626Z on thread 17
i = 3 at 2021-01-06T05:41:36.647828Z on thread 17
i = 4 at 2021-01-06T05:41:36.647902Z on thread 17
i = 0 at 2021-01-06T05:41:36.628842Z on thread 14
i = 1 at 2021-01-06T05:41:36.648148Z on thread 14
i = 2 at 2021-01-06T05:41:36.648227Z on thread 14
i = 3 at 2021-01-06T05:41:36.648294Z on thread 14
i = 4 at 2021-01-06T05:41:36.648365Z on thread 14
i = 0 at 2021-01-06T05:41:36.628837Z on thread 16
i = 1 at 2021-01-06T05:41:36.648839Z on thread 16
i = 2 at 2021-01-06T05:41:36.648919Z on thread 16
i = 3 at 2021-01-06T05:41:36.648991Z on thread 16
i = 4 at 2021-01-06T05:41:36.649054Z on thread 16

跨线程共享状态

如果您真的想在线程之间共享值,可以在直接任务代码之外定义它们。
在下一个示例中,我们定义一个类 Counter 实现 Runnable . 作为一个 Runnable 我们可以将此类的示例传递给执行器服务。我们定义了一个成员字段 ConcurrentMap (线程安全) Map )跟踪我们想要的数字0-4。对于这五个数字中的每一个,我们Map到虚拟线程的id号,该虚拟线程能够击败其他虚拟线程,从而将该条目提交到最初的空Map中。
请注意,我们正在提交一份 Counter 对象到所有三个线程。所以这三个线程都可以访问相同的 ConcurrentMap 对象。这就是为什么我们必须使用 ConcurrentMap 而不是平原 Map . 任何跨线程共享的资源都必须构建为线程安全的。
我们正在打电话 Thread.sleep 把事情搞混。否则,第一个线程可能会在主线程仍提交给第二个和第三个线程时完成所有工作。

package work.basil.example;

import java.time.Duration;
import java.time.Instant;
import java.util.concurrent.*;

public class Counter implements Runnable
{
    public ConcurrentMap < Integer, Long > results = new ConcurrentHashMap <>();

    @Override
    public void run ( )
    {
        try { Thread.sleep( Duration.ofMillis( 100 ) ); } catch ( InterruptedException e ) { e.printStackTrace(); }
        Long threadId = Thread.currentThread().getId();  // ID of this thread.
        for ( int i = 0 ; i < 5 ; i++ )
        {
            // Shake things up by waiting some random time.
            try { Thread.sleep( Duration.ofMillis( ThreadLocalRandom.current().nextInt(1, 100) ) ); } catch ( InterruptedException e ) { e.printStackTrace(); }
            results.putIfAbsent( i , threadId );  // Auto-boxing converts the `int` value of `i` to be wrapped as a `Integer` object.
        }
    }

}

这是一个 main 让我们练习的方法。

public static void main ( String[] args )
    {
        Counter counter = new Counter();
        try
                (
                        ExecutorService executorService = Executors.newVirtualThreadExecutor() ;
                )
        {
            executorService.submit( counter );
            executorService.submit( counter );
            executorService.submit( counter );
        }
        // At this point, the flow-of-control blocks until all submitted tasks are done.
        // And the executor service is also shutdown by this point.
        System.out.println( "counter.results = " + counter.results );
    }

在这个特定运行的结果中,我们可以看到编号为16和17的两个线程成功地将条目放入了Map中。第三个线程不能第一个放入五个条目中的任何一个。
counter.results={0=16,1=17,2=17,3=16,4=16}

相关问题